diff --git a/clones/_clones.md b/clones/_clones.md deleted file mode 100755 index 67c8acb..0000000 --- a/clones/_clones.md +++ /dev/null @@ -1,196 +0,0 @@ -# registry-credentials-service Clone Synchronization Plan - -## Executive Summary - -registry-credentials-service serves as the **parent framework** for a diverse ecosystem of production services. This document establishes a two-way feedback process to keep registry-credentials-service and its clones synchronized, enabling continuous improvement and pattern sharing across the entire ecosystem. - -## Clone Ecosystem Overview - -### Production Maturity Levels -- **🏛️ Grandfather**: AMS (145+ services, enterprise production) -- **🚀 Revolutionary**: Maestro (CloudEvents, gRPC, 200k+ clusters) -- **⭐ Gold Standard**: OCM AI (fully integrated registry-credentials-service-core) -- **🔒 Specialized**: ATS (audit/compliance patterns) -- **🏗️ Domain-Specific**: ABE (build/deployment workflows) - -### Integration Status -- **✅ Fully Integrated**: OCM AI (registry-credentials-service-core v0.0.0-20250711220747-a9ce95f9f591) -- **❌ Missing Core**: AMS, Maestro, ATS, ABE (require registry-credentials-service-core integration) -- **⚠️ Generator Issues**: Known service locator pattern matching bugs - -## Two-Way Feedback Process - -### 1. registry-credentials-service → Clone Updates (Framework Distribution) - -#### Update Trigger Events -- **Core Library Release**: New registry-credentials-service-core version -- **Generator Improvements**: Fixed bugs or new features -- **Framework Enhancements**: New patterns or best practices -- **Security Updates**: Critical security patches - -#### Update Process -1. **Notification**: Update FEEDBACK.md in each clone with new changes -2. **Assessment**: Clone teams evaluate impact and compatibility -3. **Integration**: Scheduled update implementation -4. **Verification**: Testing and validation of updates -5. **Feedback**: Report results back to registry-credentials-service via FEEDBACK.md - -### 2. Clone → registry-credentials-service Feedback (Pattern Backporting) - -#### Backporting Categories -- **🔥 High-Value**: Revolutionary patterns (Maestro's CloudEvents) -- **🛡️ Security**: Audit/compliance patterns (ATS) -- **⚡ Performance**: Optimization patterns (AMS production scale) -- **🏗️ Deployment**: Build/deployment patterns (ABE) -- **📊 Observability**: Monitoring/metrics patterns - -#### Backporting Process -1. **Pattern Discovery**: Clone identifies valuable pattern -2. **Documentation**: Pattern documented in FEEDBACK.md -3. **Analysis**: registry-credentials-service team evaluates backporting potential -4. **Abstraction**: Pattern generalized for framework inclusion -5. **Integration**: Pattern added to registry-credentials-service core or templates -6. **Distribution**: Enhanced framework shared with all clones - -## FEEDBACK.md Template - -Each clone maintains a FEEDBACK.md file with this structure: - -```markdown -# registry-credentials-service Clone Feedback - -## Clone Status -- **registry-credentials-service Core Version**: [version or "NOT INTEGRATED"] -- **Last Update**: [date] -- **Integration Status**: [CURRENT/OUTDATED/BLOCKED] - -## Requested Updates -- [List of registry-credentials-service updates needed] - -## Reported Issues -- [Generator bugs, framework issues, etc.] - -## Backporting Candidates -- [Valuable patterns for registry-credentials-service framework] - -## Synchronization Log -- [History of updates and changes] -``` - -## Synchronization Workflows - -### Weekly Sync Process -1. **Monday**: registry-credentials-service reviews all clone FEEDBACK.md files -2. **Tuesday**: Clone teams update their FEEDBACK.md status -3. **Wednesday**: registry-credentials-service prioritizes backporting opportunities -4. **Thursday**: Framework updates prepared and documented -5. **Friday**: Updates distributed to clones via FEEDBACK.md - -### Monthly Deep Sync -1. **Week 1**: Comprehensive clone analysis and pattern discovery -2. **Week 2**: Major backporting evaluation and implementation -3. **Week 3**: Framework testing and validation -4. **Week 4**: Major update distribution and clone integration - -## Priority Synchronization Tasks - -### Immediate Actions (Q1 2025) -1. **Core Library Integration**: All clones integrate registry-credentials-service-core -2. **Generator Bug Fixes**: Resolve service locator pattern matching -3. **FEEDBACK.md Deployment**: Establish feedback files in all clones -4. **Maestro Pattern Analysis**: Evaluate CloudEvents backporting - -### Medium-Term Goals (Q2 2025) -1. **AMS Modernization**: Apply registry-credentials-service patterns to grandfather service -2. **Security Framework**: Backport ATS audit/compliance patterns -3. **Performance Optimization**: Integrate AMS production patterns -4. **Observability Standard**: Establish monitoring framework - -### Long-Term Vision (Q3-Q4 2025) -1. **Distributed Systems Framework**: Maestro patterns fully integrated -2. **Enterprise Template**: AMS patterns abstracted for reuse -3. **Compliance Framework**: ATS patterns standardized -4. **DevOps Integration**: ABE patterns incorporated - -## Clone-Specific Synchronization Plans - -### AMS (Grandfather Service) -- **Modernization Strategy**: Gradual registry-credentials-service pattern adoption -- **Backporting Priority**: Production-scale patterns and optimizations -- **Integration Timeline**: 6-month phased approach - -### Maestro (Revolutionary) -- **Pattern Extraction**: CloudEvents, gRPC, distributed systems -- **Framework Impact**: Transform registry-credentials-service into distributed systems template -- **Integration Priority**: Highest - revolutionary capabilities - -### OCM AI (Gold Standard) -- **Role**: Reference implementation and testing platform -- **Responsibility**: Validate new registry-credentials-service patterns and updates -- **Sync Status**: Maintain cutting-edge integration - -### ATS (Specialized Security) -- **Focus**: Audit, compliance, and security patterns -- **Backporting Value**: Enterprise security framework -- **Integration**: Security-focused registry-credentials-service enhancements - -### ABE (DevOps Integration) -- **Domain**: Build, deployment, and environment management -- **Patterns**: DevOps workflow integration -- **Value**: Complete development lifecycle support - -## Success Metrics - -### Synchronization Health -- **Integration Rate**: % of clones with current registry-credentials-service-core -- **Update Velocity**: Time from registry-credentials-service update to clone integration -- **Backporting Volume**: Number of clone patterns integrated into registry-credentials-service -- **Bug Resolution**: Time to fix and distribute generator/framework issues - -### Ecosystem Growth -- **Pattern Library**: Number of reusable patterns in registry-credentials-service -- **Clone Satisfaction**: Feedback quality and engagement -- **Framework Evolution**: Rate of registry-credentials-service capability expansion -- **Production Impact**: Improvement in clone development velocity - -## Governance - -### Roles and Responsibilities -- **registry-credentials-service Team**: Framework development, pattern integration, update distribution -- **Clone Teams**: Pattern development, feedback provision, update integration -- **Claude Assistant**: Pattern analysis, documentation, synchronization facilitation - -### Communication Channels -- **Primary**: FEEDBACK.md files in each clone repository -- **Secondary**: Regular sync meetings and documentation updates -- **Emergency**: Direct communication for critical security or bug fixes - -## Implementation Timeline - -### Phase 1: Foundation (Weeks 1-4) -- Deploy FEEDBACK.md to all clones -- Establish weekly sync process -- Begin core library integration - -### Phase 2: Synchronization (Weeks 5-12) -- Complete core library integration -- Fix generator bugs -- Implement first backporting cycle - -### Phase 3: Optimization (Weeks 13-24) -- Establish monthly deep sync -- Major pattern backporting (Maestro, AMS) -- Framework enhancement integration - -### Phase 4: Maturation (Weeks 25-52) -- Full ecosystem synchronization -- Advanced pattern integration -- Self-sustaining feedback loops - -## Conclusion - -This synchronization plan establishes registry-credentials-service as the central hub of an interconnected ecosystem of production services. Through systematic two-way feedback, registry-credentials-service evolves from a simple template into a comprehensive distributed systems framework, while clones benefit from continuous improvements and shared innovations. - -The success of this plan depends on consistent execution of the feedback process, proactive pattern identification, and commitment to maintaining synchronization across the entire ecosystem. - -**Next Steps**: Deploy FEEDBACK.md templates to all clones and begin immediate core library integration across the ecosystem. \ No newline at end of file diff --git a/clones/abe.md b/clones/abe.md deleted file mode 100755 index 7b3136d..0000000 --- a/clones/abe.md +++ /dev/null @@ -1,56 +0,0 @@ -# ABE Clone Summary - -## Clone Information -- **Location**: `/home/mturansk/projects/src/gitlab.cee.redhat.com/ocm/abe` -- **Clone Status**: ✅ **LINKAGE ESTABLISHED** -- **Repository**: GitLab CEE Red Hat (internal) -- **Domain**: Application Build Environment - -## Business Domain -ABE (Application Build Environment) is a specialized registry-credentials-service clone focused on application build and deployment workflows. It provides CRUD operations for managing build environments, deployment pipelines, and application lifecycle management. - -## Architecture Assessment -- **Framework**: Standard registry-credentials-service patterns with build-specific customizations -- **Database**: PostgreSQL with GORM ORM -- **API**: REST API with OpenAPI specification -- **Authentication**: OCM integration for Red Hat authentication -- **Testing**: Unit and integration test frameworks - -## registry-credentials-service Integration Status -- **Clone Recognition**: ✅ **ESTABLISHED** - CLAUDE.md updated with registry-credentials-service linkage -- **Core Library**: ❌ **NOT INTEGRATED** - Missing registry-credentials-service-core dependency -- **Generator**: ⚠️ **UNKNOWN** - Generator status not verified -- **Framework Version**: Legacy registry-credentials-service patterns - -## Key Characteristics -- **Build Management**: Specialized for application build environments -- **Deployment Workflows**: Custom deployment pipeline management -- **Environment Configuration**: Multi-environment support for builds -- **Integration Points**: Red Hat internal tooling integration - -## Critical Issues -1. **Missing Core Library**: No registry-credentials-service-core integration -2. **Framework Consistency**: Using legacy registry-credentials-service patterns -3. **Update Process**: No systematic registry-credentials-service update procedure documented - -## Update Recommendations -1. **Priority 1**: Integrate registry-credentials-service-core library -2. **Priority 2**: Update transaction patterns to use core library -3. **Priority 3**: Establish regular registry-credentials-service update procedures -4. **Priority 4**: Document build-specific patterns for potential backporting - -## Documentation Status -- **CLAUDE.md**: ✅ **CREATED** - registry-credentials-service linkage documented -- **FEEDBACK.md**: ✅ **CREATED** - Clone analysis and recommendations -- **Update Procedures**: ✅ **DOCUMENTED** - Systematic registry-credentials-service update process - -## Backporting Potential -- **Build Pipeline Patterns**: Specialized build environment management -- **Deployment Workflows**: Custom deployment pipeline implementations -- **Environment Management**: Multi-environment configuration patterns - -## Future Linkage Notes -- Internal Red Hat project with potential enterprise patterns -- Build-specific domain knowledge could benefit registry-credentials-service ecosystem -- Requires access to GitLab CEE for updates and analysis -- Standard registry-credentials-service clone requiring core library integration \ No newline at end of file diff --git a/clones/ams.md b/clones/ams.md deleted file mode 100755 index 3d159ab..0000000 --- a/clones/ams.md +++ /dev/null @@ -1,131 +0,0 @@ -# Account Management Services (AMS) Clone Summary - -## Clone Information -- **Location**: `/home/mturansk/projects/src/gitlab.cee.redhat.com/service/uhc-account-manager` -- **Clone Status**: 🏛️ **GRANDFATHER** - Original service that influenced registry-credentials-service design -- **Repository**: GitLab (internal Red Hat) -- **Domain**: Enterprise Account & Subscription Management - -## Business Domain -Account Management Services (AMS) is the **production-scale enterprise service** that manages the complete lifecycle of Red Hat OpenShift Cloud Management (OCM) platform operations. It handles organizations, accounts, subscriptions, clusters, billing, and compliance for thousands of enterprise customers. - -## Architecture Assessment - MATURE PRODUCTION SYSTEM -- **Framework**: Custom REST framework built on Gorilla Mux -- **Database**: PostgreSQL with GORM ORM and complex entity relationships -- **API**: OpenAPI 3.0 with extensive REST endpoints -- **Authentication**: JWT-based with custom middleware -- **Scale**: 1,456 Go files, 116,990 lines of code -- **Complexity**: 145+ services in single service locator - -## registry-credentials-service Relationship - THE GRANDFATHER -- **Historical Role**: ✅ **ARCHITECTURAL ANCESTOR** - Original service whose patterns influenced registry-credentials-service -- **Legacy Status**: 🏛️ **GRANDFATHER** - AMS patterns → Example patterns → registry-credentials-service framework -- **Evolution**: AMS complexity → registry-credentials-service simplification → Modern microservice patterns -- **Influence**: Service locator, environment management, database patterns, handler framework - -## Key Characteristics - ENTERPRISE PRODUCTION SCALE -- **Production Maturity**: Battle-tested enterprise service serving thousands of customers -- **Complex Business Logic**: Multi-tenant organization, subscription lifecycle, cluster authorization -- **External Integrations**: RHIT, Candlepin, Quay, RBAC, Telemeter, Segment, Unleash -- **Compliance**: FedRAMP compliance, export control screening, audit logging -- **Operational Excellence**: Blue-green deployment, canary routing, comprehensive monitoring - -## Technical Implementation -- **Service Locator Pattern**: 145+ services in monolithic Services struct -- **Environment Management**: Complex environment-specific configuration -- **Database Architecture**: Heavy DAO pattern with complex entity relationships -- **Handler Framework**: Custom REST framework with validation and error handling -- **Testing**: Comprehensive test suite with unit, integration, API, and benchmark tests - -## Legacy Patterns That Became registry-credentials-service -### **Service Locator Evolution** -- **AMS Pattern**: 145+ services in single struct with function-based locators -- **registry-credentials-service Evolution**: Simplified service locators with 3-5 focused services - -### **Environment Management** -- **AMS Pattern**: Complex environment-specific implementations -- **registry-credentials-service Evolution**: Streamlined environment management - -### **Database Abstraction** -- **AMS Pattern**: Heavy DAO pattern with complex connection factory -- **registry-credentials-service Evolution**: Clean service layer with simplified session factory - -### **Handler Framework** -- **AMS Pattern**: Custom handler framework with complex validation -- **registry-credentials-service Evolution**: Simplified CRUD patterns with reduced boilerplate - -## Known Complexity Issues -1. **Service Locator Bloat**: 145+ services in single struct causing maintenance overhead -2. **Configuration Complexity**: Environment-specific complexity across multiple services -3. **Database Layer Weight**: Heavy DAO pattern with tight GORM coupling -4. **Manual Maintenance**: Extensive manual API maintenance without generation -5. **Testing Complexity**: Complex test setup due to architectural complexity - -## Update Status -- **Framework**: 🏛️ **LEGACY** - Custom framework predating registry-credentials-service patterns -- **Architecture**: 🏛️ **MONOLITHIC** - Single service handling massive business domain -- **Patterns**: 🏛️ **ORIGINAL** - Source patterns that influenced registry-credentials-service design -- **Maintenance**: 🏛️ **MANUAL** - No automated generation, extensive manual maintenance - -## Documentation Status -- **Production Docs**: ✅ **COMPREHENSIVE** - Extensive enterprise documentation -- **API Documentation**: ✅ **COMPLETE** - OpenAPI 3.0 specifications -- **Operational Docs**: ✅ **MATURE** - Production deployment and monitoring -- **Architecture Docs**: ✅ **EXTENSIVE** - Complex system documentation - -## Modernization Potential - MASSIVE OPPORTUNITY -AMS represents the **largest modernization opportunity** in the registry-credentials-service ecosystem: - -### High-Impact Modernization Areas -1. **Service Locator Simplification**: Reduce 145+ services to focused service groups -2. **Handler Framework Modernization**: Adopt registry-credentials-service's cleaner CRUD patterns -3. **Database Layer Cleanup**: Migrate from complex DAO to registry-credentials-service's cleaner approach -4. **Configuration Simplification**: Streamline environment-specific complexity -5. **API Generation**: Adopt registry-credentials-service's generator patterns for consistency - -### Architectural Transformation -- **Monolith Decomposition**: Break down massive service into focused microservices -- **Pattern Modernization**: Apply registry-credentials-service's refined patterns to legacy code -- **Generation Integration**: Introduce automated code generation for consistency -- **Testing Simplification**: Reduce complex test setup through cleaner architecture - -## Clone Utilization - PRODUCTION ENTERPRISE SERVICE -- **Primary Purpose**: Production OCM platform account and subscription management -- **Secondary Purpose**: Architectural pattern laboratory for registry-credentials-service framework -- **Tertiary Purpose**: Modernization case study for enterprise service evolution - -## Future Linkage Notes -- **Internal GitLab repository** requiring special access and analysis -- **Production service** with thousands of enterprise customers -- **Architectural ancestor** demonstrating evolution to registry-credentials-service patterns -- **Modernization target** for applying registry-credentials-service framework improvements -- **Pattern source** for understanding registry-credentials-service's architectural decisions - -## Strategic Value -- **Historical Significance**: Original service that influenced registry-credentials-service design -- **Pattern Laboratory**: Source of proven enterprise patterns -- **Modernization Case Study**: Demonstrates evolution from legacy to registry-credentials-service patterns -- **Production Validation**: Battle-tested patterns in enterprise environment -- **Architectural Evolution**: Shows progression from monolithic to modular design - -## Legacy vs Modern Comparison - -| Aspect | AMS (Legacy) | registry-credentials-service (Modern) | -|--------|-------------|---------------| -| Service Count | 145+ services | 3-5 focused services | -| Configuration | Complex env-specific | Streamlined config | -| Database | Heavy DAO pattern | Light service pattern | -| Generation | Manual maintenance | Automated generation | -| Testing | Complex setup | Simplified testing | -| Deployment | Manual templates | Automated deployment | - -## Recommendation -AMS represents the **grandfather service** that pioneered the patterns registry-credentials-service refined. While maintaining production stability, AMS would benefit significantly from **gradual modernization** using registry-credentials-service framework patterns: - -1. **Incremental Migration**: Apply registry-credentials-service patterns to new features -2. **Service Decomposition**: Break down monolithic services into focused areas -3. **Pattern Modernization**: Adopt registry-credentials-service's refined architectural patterns -4. **Generation Integration**: Introduce automated code generation for consistency -5. **Testing Simplification**: Reduce complexity through cleaner architecture - -AMS demonstrates the **architectural evolution** from complex, battle-tested systems to cleaner, more maintainable frameworks. It serves as both the **historical foundation** and **modernization opportunity** for the registry-credentials-service ecosystem. \ No newline at end of file diff --git a/clones/ats.md b/clones/ats.md deleted file mode 100755 index b09f2f5..0000000 --- a/clones/ats.md +++ /dev/null @@ -1,77 +0,0 @@ -# Access Transparency Service Clone Summary - -## Clone Information -- **Location**: `/home/mturansk/projects/src/gitlab.cee.redhat.com/ocm/access-transparency-service` -- **Clone Status**: ✅ **LINKAGE ESTABLISHED** -- **Repository**: GitLab CEE Red Hat (internal) -- **Domain**: Access Transparency and Audit - -## Business Domain -Access Transparency Service is a specialized registry-credentials-service clone focused on access transparency, audit logging, and compliance management. It provides comprehensive audit trails for access requests, approvals, and administrative actions across Red Hat's infrastructure. - -## Architecture Assessment -- **Framework**: Standard registry-credentials-service patterns with audit-specific customizations -- **Database**: PostgreSQL with GORM ORM for audit data persistence -- **API**: REST API with OpenAPI specification for audit operations -- **Authentication**: OCM integration with enhanced security for audit data -- **Compliance**: Specialized audit logging and retention policies - -## registry-credentials-service Integration Status -- **Clone Recognition**: ✅ **ESTABLISHED** - CLAUDE.md updated with registry-credentials-service linkage -- **Core Library**: ❌ **NOT INTEGRATED** - Missing registry-credentials-service-core dependency -- **Generator**: ⚠️ **UNKNOWN** - Generator status not verified -- **Framework Version**: Legacy registry-credentials-service patterns - -## Key Characteristics -- **Audit Management**: Comprehensive audit trail management -- **Access Transparency**: Transparent access request and approval workflows -- **Compliance Reporting**: Specialized compliance and audit reporting -- **Security Focus**: Enhanced security patterns for sensitive audit data -- **Retention Policies**: Audit data retention and archival management - -## Critical Issues -1. **Missing Core Library**: No registry-credentials-service-core integration -2. **Framework Consistency**: Using legacy registry-credentials-service patterns -3. **Update Process**: No systematic registry-credentials-service update procedure documented - -## Update Recommendations -1. **Priority 1**: Integrate registry-credentials-service-core library -2. **Priority 2**: Update transaction patterns to use core library -3. **Priority 3**: Establish regular registry-credentials-service update procedures -4. **Priority 4**: Document audit-specific patterns for potential backporting - -## Documentation Status -- **CLAUDE.md**: ✅ **CREATED** - registry-credentials-service linkage documented with strict file preservation rules -- **FEEDBACK.md**: ✅ **CREATED** - Clone analysis and recommendations -- **Update Procedures**: ✅ **DOCUMENTED** - Systematic registry-credentials-service update process -- **File Preservation**: ✅ **ENFORCED** - NEVER delete CLAUDE.md or FEEDBACK.md - -## Backporting Potential - HIGH VALUE -- **Audit Framework**: Comprehensive audit logging patterns -- **Access Transparency**: Access request and approval workflows -- **Compliance Patterns**: Audit retention and compliance reporting -- **Security Enhancements**: Enhanced security patterns for sensitive data -- **Retention Management**: Data retention and archival patterns - -## Virtuous Cycle Opportunity -The Access Transparency Service contains valuable audit and compliance patterns that could significantly enhance the registry-credentials-service ecosystem: - -### High-Value Backporting Candidates -1. **Audit Framework**: Comprehensive audit logging for all registry-credentials-service operations -2. **Access Control**: Enhanced access request and approval workflows -3. **Compliance Reporting**: Standardized compliance and audit reporting -4. **Security Patterns**: Enhanced security patterns for sensitive data -5. **Retention Policies**: Data retention and archival management - -### Claude-Assisted Backporting Process -- **Audit Pattern Analysis**: Use Claude to analyze audit logging patterns -- **Security Enhancement**: Have Claude design generic security improvements -- **Compliance Framework**: Use Claude to create reusable compliance patterns -- **Template Integration**: Have Claude integrate audit patterns into registry-credentials-service templates - -## Future Linkage Notes -- Internal Red Hat project with high-value audit and compliance patterns -- Security-focused domain knowledge critical for enterprise registry-credentials-service deployments -- Requires access to GitLab CEE for updates and analysis -- Standard registry-credentials-service clone requiring core library integration -- Strong candidate for systematic backporting to enhance registry-credentials-service security and compliance capabilities \ No newline at end of file diff --git a/clones/maestro.md b/clones/maestro.md deleted file mode 100755 index dc33002..0000000 --- a/clones/maestro.md +++ /dev/null @@ -1,94 +0,0 @@ -# Maestro Clone Summary - -## Clone Information -- **Location**: `/home/mturansk/projects/src/github.com/openshift-online/maestro` -- **Clone Status**: ✅ **LINKAGE ESTABLISHED** -- **Repository**: GitHub (openshift-online) -- **Domain**: Kubernetes Resource Orchestration - -## Business Domain -Maestro is the most advanced registry-credentials-service clone - a revolutionary distributed systems architecture for Kubernetes resource orchestration. It manages Kubernetes resources across 200,000+ clusters using CloudEvents, gRPC, and message broker architecture. - -## Architecture Assessment - REVOLUTIONARY -- **Framework**: Advanced distributed systems transcending standard registry-credentials-service patterns -- **Database**: PostgreSQL with GORM ORM for resource state management -- **API**: Dual REST and gRPC APIs with CloudEvents integration -- **Authentication**: OCM integration with advanced cluster authentication -- **Scalability**: Designed for massive scale (200,000+ clusters) -- **Message Transport**: MQTT and Kafka integration for CloudEvents - -## registry-credentials-service Integration Status -- **Clone Recognition**: ✅ **ESTABLISHED** - CLAUDE.md updated with registry-credentials-service linkage -- **Core Library**: ❌ **NOT INTEGRATED** - Missing registry-credentials-service-core dependency -- **Generator**: ❌ **NOT FOUND** - May have evolved beyond standard entity generation -- **Framework Version**: Advanced evolution beyond standard registry-credentials-service patterns - -## Key Characteristics - CUTTING-EDGE -- **CloudEvents Protocol**: Event-driven architecture for resource transport -- **Distributed Systems**: Hash-based distribution, async processing -- **gRPC Integration**: High-performance gRPC APIs alongside REST -- **Message Brokers**: MQTT and Kafka integration for scalable messaging -- **Kubernetes Native**: Deep OCM, ManifestWork, and Kubernetes integration -- **Performance Engineering**: Connection pooling, caching, performance optimization -- **Advanced Observability**: OpenTelemetry, Prometheus, Grafana dashboards -- **Production Scale**: Live system managing resources at unprecedented scale - -## Critical Issues -1. **Missing Core Library**: No registry-credentials-service-core integration despite advanced architecture -2. **Framework Inconsistency**: Using legacy transaction patterns -3. **Generator Evolution**: May have transcended standard registry-credentials-service generator patterns - -## Update Recommendations -1. **Priority 1**: Integrate registry-credentials-service-core library for framework consistency -2. **Priority 2**: Analyze architectural patterns for framework alignment -3. **Priority 3**: Document revolutionary patterns for registry-credentials-service ecosystem backporting - -## Documentation Status -- **CLAUDE.md**: ✅ **CREATED** - registry-credentials-service linkage documented with advanced architecture -- **FEEDBACK.md**: ✅ **CREATED** - Grade A+++ assessment with revolutionary analysis -- **Update Procedures**: ✅ **DOCUMENTED** - Systematic registry-credentials-service update process -- **File Preservation**: ✅ **ENFORCED** - NEVER delete CLAUDE.md or FEEDBACK.md - -## Backporting Potential - REVOLUTIONARY VALUE -Maestro represents a generational leap that could transform the entire registry-credentials-service ecosystem: - -### Revolutionary Backporting Candidates -1. **CloudEvents Framework**: Event-driven architecture for any domain -2. **gRPC Integration**: High-performance gRPC templates and patterns -3. **Distributed Systems**: Hash-based distribution and async processing -4. **Message Broker Framework**: MQTT and Kafka integration patterns -5. **Advanced Observability**: OpenTelemetry integration for all registry-credentials-service services -6. **Performance Optimization**: Connection pooling, caching, and performance patterns -7. **Kubernetes Native**: OCM and ManifestWork patterns for Kubernetes domains - -### Architectural Innovations -- **Dual API Architecture**: REST and gRPC coexistence patterns -- **Event-Driven Design**: CloudEvents protocol implementation -- **Scalability Patterns**: Massive scale architecture (200,000+ entities) -- **Performance Engineering**: Advanced performance optimization techniques -- **Observability Excellence**: Comprehensive monitoring and tracing - -## Virtuous Cycle Opportunity - MAXIMUM IMPACT -Maestro's innovations could revolutionize registry-credentials-service from a microservice template to a distributed systems framework: - -### Claude-Assisted Backporting Process -1. **Architectural Analysis**: Use Claude to analyze distributed systems patterns -2. **Pattern Abstraction**: Have Claude design generic versions of Maestro innovations -3. **Template Integration**: Use Claude to integrate improvements into registry-credentials-service templates -4. **Framework Enhancement**: Have Claude document new distributed systems patterns -5. **Testing**: Use Claude to create comprehensive test cases for new features - -### Ecosystem Impact -- **Revolutionary registry-credentials-service**: Transform main framework into distributed systems template -- **Advanced Clones**: Future clones inherit CloudEvents and gRPC patterns -- **Performance Excellence**: All clones benefit from performance optimizations -- **Observability Standard**: OpenTelemetry becomes registry-credentials-service standard -- **Kubernetes Ecosystem**: registry-credentials-service becomes premier Kubernetes development framework - -## Future Linkage Notes -- Public GitHub repository enabling easier analysis and updates -- Production system with proven scalability and performance -- Most advanced registry-credentials-service evolution demonstrating framework's ultimate potential -- **Critical**: Missing registry-credentials-service-core integration despite revolutionary architecture -- **Opportunity**: Revolutionary patterns could transform entire registry-credentials-service ecosystem -- **Recommendation**: Priority candidate for systematic backporting to create next-generation registry-credentials-service framework \ No newline at end of file diff --git a/clones/ocmai.md b/clones/ocmai.md deleted file mode 100755 index d459ed8..0000000 --- a/clones/ocmai.md +++ /dev/null @@ -1,108 +0,0 @@ -# OCM AI Clone Summary - -## Clone Information -- **Location**: `/home/mturansk/projects/src/github.com/openshift-online/ocmai` -- **Clone Status**: ✅ **LINKAGE ESTABLISHED** -- **Repository**: GitHub (openshift-online) -- **Domain**: Microservice Template Framework / Instant API Platform - -## Business Domain -OCM AI is a specialized registry-credentials-service clone serving as a **microservice template framework** and **Instant API platform**. It demonstrates the complete registry-credentials-service framework capabilities through template-based rapid API development and serves as a foundation for creating new enterprise microservices. - -## Architecture Assessment - FULLY INTEGRATED -- **Framework**: Complete registry-credentials-service framework integration with latest patterns -- **Database**: PostgreSQL with GORM ORM and advanced features -- **API**: REST APIs with complete OpenAPI 3.0 specification -- **Authentication**: OIDC with OCM integration -- **Code Generation**: Template-based entity and API generation -- **Testing**: Comprehensive unit and integration testing framework - -## registry-credentials-service Integration Status - EXEMPLARY -- **Clone Recognition**: ✅ **ESTABLISHED** - Explicit registry-credentials-service clone documentation -- **Core Library**: ✅ **FULLY INTEGRATED** - Using registry-credentials-service-core v0.0.0-20250711220747-a9ce95f9f591 -- **Generator**: ✅ **PRESENT** - Complete generator script with known bugs -- **Framework Version**: Latest registry-credentials-service patterns with full core library integration - -## Key Characteristics - TEMPLATE PLATFORM -- **Instant API Framework**: Creates complete REST APIs rapidly -- **Template-Based Generation**: Scaffolds full CRUD operations -- **Complete Integration**: Demonstrates all registry-credentials-service framework capabilities -- **Multi-Entity Support**: Handles complex entity relationships -- **Event-Driven Architecture**: PostgreSQL LISTEN/NOTIFY for async processing -- **Clean Architecture**: Layered design with clear separation of concerns - -## Technical Implementation -- **Service Locator Pattern**: Proper dependency injection -- **Database Management**: Migrations, advisory locking, connection pooling -- **OpenAPI Integration**: Auto-generated documentation and client SDKs -- **Environment Management**: Multi-environment support (dev, test, prod) -- **Monitoring**: Comprehensive metrics, logging, and health checks - -## Generator Capabilities -- **Entity Generation**: `go run ./scripts/generator.go --kind EntityName` -- **Complete CRUD**: Auto-generates API, service, DAO layers -- **OpenAPI Updates**: Automatically updates OpenAPI specifications -- **Database Migrations**: Generates migration files for new entities -- **Template System**: Uses template-based code generation - -## Known Issues -1. **Generator Bugs**: Pattern matching issues in service locator registration -2. **Manual Cleanup Required**: Post-generation cleanup per CLONING.md -3. **Migration Files**: Generator creates incomplete migration files -4. **Service Locator Registration**: Overly broad pattern matching corrupts structs - -## Demonstration Projects -**Evidence of successful API generation**: -- **Circus Management System**: Complete demo with Performer, Act, Show, Venue entities -- **Kubernetes Management**: Cluster, Namespace, Application, Deployment entities - -## Update Status -- **Core Library**: ✅ **LATEST VERSION** - Using current registry-credentials-service-core -- **Framework Patterns**: ✅ **UP TO DATE** - Latest registry-credentials-service patterns -- **Transaction Management**: ✅ **CURRENT** - Using core library transactions -- **Testing Framework**: ✅ **COMPREHENSIVE** - Full test coverage - -## Documentation Status -- **CLAUDE.md**: ✅ **COMPREHENSIVE** - Complete registry-credentials-service clone documentation -- **CLONING.md**: ✅ **DETAILED** - Post-clone cleanup procedures -- **Generator Documentation**: ✅ **COMPLETE** - Known issues documented -- **Update Procedures**: ✅ **ESTABLISHED** - Systematic framework updates - -## Backporting Potential - TEMPLATE EXCELLENCE -OCM AI represents the **gold standard** for registry-credentials-service template implementation: - -### High-Value Patterns -1. **Template Framework**: Advanced template-based code generation -2. **Multi-Entity Management**: Complex entity relationship handling -3. **Event-Driven Architecture**: PostgreSQL LISTEN/NOTIFY patterns -4. **Clean Architecture**: Exemplary layered design implementation -5. **Testing Excellence**: Comprehensive test framework patterns - -### Template Innovation -- **Instant API Platform**: Rapid API development capabilities -- **Complete Integration**: Full framework utilization demonstration -- **Code Generation Excellence**: Advanced template-based generation -- **Multi-Environment Support**: Sophisticated environment management - -## Clone Utilization - FRAMEWORK DEMONSTRATOR -- **Primary Purpose**: Serves as registry-credentials-service framework demonstration and template -- **Secondary Purpose**: Foundation for new microservice development -- **Tertiary Purpose**: Testing platform for registry-credentials-service framework improvements - -## Future Linkage Notes -- **Public GitHub repository** enabling easy analysis and updates -- **Template platform** demonstrating complete registry-credentials-service capabilities -- **Up-to-date integration** with latest registry-credentials-service patterns and core library -- **Generator platform** for testing framework improvements -- **Gold standard** for registry-credentials-service clone implementation -- **Framework demonstration** showing registry-credentials-service's full potential - -## Strategic Value -- **Template Excellence**: Demonstrates optimal registry-credentials-service implementation -- **Framework Validation**: Validates registry-credentials-service design patterns -- **Integration Testing**: Tests core library integration -- **Documentation Reference**: Exemplary registry-credentials-service clone documentation -- **Training Platform**: Ideal for learning registry-credentials-service framework patterns - -## Recommendation -OCM AI serves as the **reference implementation** for registry-credentials-service clones, demonstrating complete framework integration and serving as a template for new microservice development. It represents the current state of registry-credentials-service framework excellence. \ No newline at end of file diff --git a/cmd/registry-credentials-service/clone/cmd.go b/cmd/registry-credentials-service/clone/cmd.go deleted file mode 100755 index 5f5d293..0000000 --- a/cmd/registry-credentials-service/clone/cmd.go +++ /dev/null @@ -1,201 +0,0 @@ -package clone - -import ( - "flag" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/golang/glog" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -type provisionCfgFlags struct { - Name string - RepoBase string - Destination string -} - -func (c *provisionCfgFlags) AddFlags(fs *pflag.FlagSet) { - fs.StringVar(&c.Name, "name", c.Name, "Name of the new service being provisioned") - fs.StringVar(&c.Destination, "destination", c.Destination, "Target directory for the newly provisioned instance") - fs.StringVar(&c.RepoBase, "repo-base", c.RepoBase, "Repository base URL (e.g., 'github.com/openshift-online')") -} - -var provisionCfg = &provisionCfgFlags{ - Name: "registry-credential-service", - RepoBase: "github.com/openshift-online", - Destination: "/tmp/clone-test", -} - -// NewCloneCommand sub-command handles cloning a new registry-credential-service instance -func NewCloneCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "clone", - Short: "Clone a new registry-credential-service instance", - Long: "Clone a new registry-credential-service instance", - Run: clone, - } - - provisionCfg.AddFlags(cmd.PersistentFlags()) - cmd.PersistentFlags().AddGoFlagSet(flag.CommandLine) - return cmd -} - -var rw os.FileMode = 0777 - -func clone(_ *cobra.Command, _ []string) { - - glog.Infof("creating new registry-credential-service instance as %s in directory %s", provisionCfg.Name, provisionCfg.Destination) - - originalDestination := provisionCfg.Destination - - // walk the filesystem, starting at the root of the project - err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - // ignore git subdirectories - if path == ".git" || strings.Contains(path, ".git/") { - return nil - } - - // Replace "registry-credential-service" only in the relative path - modifiedPath := strings.ReplaceAll(path, "registry-credential-service", strings.ToLower(provisionCfg.Name)) - dest := filepath.Join(originalDestination, modifiedPath) - - if info.IsDir() { - // does this path exist in the destination? - if _, err := os.Stat(dest); os.IsNotExist(err) { - glog.Infof("Directory does not exist, creating: %s", dest) - } - - err := os.MkdirAll(dest, rw) - if err != nil { - return err - } - - } else { - content, err := config.ReadFile(path) - if err != nil { - return err - } - - content = strings.ReplaceAll(content, "github.com/openshift-hyperfleet/registry-credentials-service", "github.com/openshift-hyperfleet/registry-credentials-service") - // Only replace github.com/openshift-online when it's used for generator hardcode, not for dependencies - content = strings.ReplaceAll(content, `= "github.com/openshift-online"`, `= "github.com/openshift-online"`) - content = strings.ReplaceAll(content, "ApiRegistryCredentialServiceV1", "ApiRegistryCredentialServiceV1") - content = strings.ReplaceAll(content, "registry-credential-service", "registry-credential-service") - content = strings.ReplaceAll(content, "registry-credential-service", "registry-credential-service") - content = strings.ReplaceAll(content, "registry-credential-service", "registry-credential-service") - content = strings.ReplaceAll(content, "registry-credential-service", "registry-credential-service") - content = strings.ReplaceAll(content, "registry-credential-service", "registry-credential-service") - - replacement := fmt.Sprintf("%s/%s", provisionCfg.RepoBase, strings.ToLower(provisionCfg.Name)) - content = strings.ReplaceAll(content, "github.com/openshift-hyperfleet/registry-credentials-service", replacement) - content = strings.ReplaceAll(content, `= "github.com/openshift-online"`, fmt.Sprintf(`= "%s"`, provisionCfg.RepoBase)) - // For example, convert the service name "rh-birds" to "RhBirds" - serviceName := strings.ToLower(provisionCfg.Name) - parts := strings.Split(serviceName, "-") - var titleCase string - for _, part := range parts { - if len(part) > 0 { - titleCase += strings.ToUpper(string(part[0])) + part[1:] - } - } - apiPrefix := fmt.Sprintf("Api%sV1", titleCase) - content = strings.ReplaceAll(content, "ApiRegistryCredentialServiceV1", apiPrefix) - content = strings.ReplaceAll(content, "registry-credential-service", provisionCfg.Name) - content = strings.ReplaceAll(content, "registry-credential-service", provisionCfg.Name) - content = strings.ReplaceAll(content, "registry-credential-service", strings.ToLower(provisionCfg.Name)) - content = strings.ReplaceAll(content, "registry-credential-service", strings.ToLower(provisionCfg.Name)) - content = strings.ReplaceAll(content, "registry-credential-service", strings.ToLower(provisionCfg.Name)) - - if exists(dest) { - e := os.Remove(dest) - if e != nil { - return e - } - } - - file, err := os.OpenFile(dest, os.O_APPEND|os.O_CREATE|os.O_RDWR, rw) - if err != nil { - return err - } - - written, fErr := file.WriteString(content) - if fErr != nil { - return fErr - } - - glog.Infof("wrote %d bytes for file %s", written, dest) - file.Sync() - file.Close() - } - - return nil - }) - - if err != nil { - fmt.Println(err) - return - } - - // Print next steps for the customer - serviceName := strings.ToLower(provisionCfg.Name) - msg := fmt.Sprintf(` -✅ Clone completed successfully! - -📋 Next steps to run your new service: - -1. Navigate to your new service directory: - cd %s - -2. Install dependencies: - go mod tidy - -3. Build the project: - go install gotest.tools/gotestsum@latest - make binary - -4. Set up the database: - make db/setup - -5. Run database migrations: - ./%s migrate - -6. Test the application: - make test - make test-integration - -7. Run your service (choose one option): - - Option A: Without authentication (recommended for local development): - make run-no-auth - - Option B: With authentication (production-like): - make run - -8. Verify the service is running: - - If using Option A (no auth): - curl http://localhost:8000/api/%s/v1/dinosaurs | jq - - If using Option B (with auth): - ocm login --token=${OCM_ACCESS_TOKEN} --url=http://localhost:8000 - ocm get /api/%s/v1/dinosaurs - -For more detailed information, refer to the README.md in your new service directory. -`, provisionCfg.Destination, serviceName, serviceName, serviceName) - - fmt.Println(msg) -} - -func exists(path string) bool { - _, err := os.Stat(path) - return err == nil -} diff --git a/cmd/registry-credentials-service/environments/e_development.go b/cmd/registry-credentials-service/environments/e_development.go deleted file mode 100755 index 5c15e80..0000000 --- a/cmd/registry-credentials-service/environments/e_development.go +++ /dev/null @@ -1,50 +0,0 @@ -package environments - -import ( - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/db_session" -) - -// devEnvImpl environment is intended for local use while developing features -type devEnvImpl struct { - env *Env -} - -var _ EnvironmentImpl = &devEnvImpl{} - -func (e *devEnvImpl) OverrideDatabase(c *Database) error { - c.SessionFactory = db_session.NewProdFactory(e.env.Config.Database) - return nil -} - -func (e *devEnvImpl) OverrideConfig(c *config.ApplicationConfig) error { - c.Server.EnableJWT = false - c.Server.EnableHTTPS = false - return nil -} - -func (e *devEnvImpl) OverrideServices(s *Services) error { - return nil -} - -func (e *devEnvImpl) OverrideHandlers(h *Handlers) error { - return nil -} - -func (e *devEnvImpl) OverrideClients(c *Clients) error { - return nil -} - -func (e *devEnvImpl) Flags() map[string]string { - return map[string]string{ - "v": "10", - "enable-authz": "false", - "ocm-debug": "false", - "enable-ocm-mock": "true", - "enable-https": "false", - "enable-metrics-https": "false", - "api-server-hostname": "localhost", - "api-server-bindaddress": "localhost:8000", - "enable-sentry": "false", - } -} diff --git a/cmd/registry-credentials-service/environments/e_integration_testing.go b/cmd/registry-credentials-service/environments/e_integration_testing.go deleted file mode 100755 index bd54bd2..0000000 --- a/cmd/registry-credentials-service/environments/e_integration_testing.go +++ /dev/null @@ -1,54 +0,0 @@ -package environments - -import ( - "os" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/db_session" -) - -var _ EnvironmentImpl = &integrationTestingEnvImpl{} - -// integrationTestingEnvImpl is configuration for integration tests using testcontainers -type integrationTestingEnvImpl struct { - env *Env -} - -func (e *integrationTestingEnvImpl) OverrideDatabase(c *Database) error { - c.SessionFactory = db_session.NewTestcontainerFactory(e.env.Config.Database) - return nil -} - -func (e *integrationTestingEnvImpl) OverrideConfig(c *config.ApplicationConfig) error { - // Support a one-off env to allow enabling db debug in testing - if os.Getenv("DB_DEBUG") == "true" { - c.Database.Debug = true - } - return nil -} - -func (e *integrationTestingEnvImpl) OverrideServices(s *Services) error { - return nil -} - -func (e *integrationTestingEnvImpl) OverrideHandlers(h *Handlers) error { - return nil -} - -func (e *integrationTestingEnvImpl) OverrideClients(c *Clients) error { - return nil -} - -func (e *integrationTestingEnvImpl) Flags() map[string]string { - return map[string]string{ - "v": "0", - "logtostderr": "true", - "ocm-base-url": "https://api.integration.openshift.com", - "enable-https": "false", - "enable-metrics-https": "false", - "enable-authz": "true", - "ocm-debug": "false", - "enable-ocm-mock": "true", - "enable-sentry": "false", - } -} diff --git a/cmd/registry-credentials-service/environments/e_production.go b/cmd/registry-credentials-service/environments/e_production.go deleted file mode 100755 index 86b9c8c..0000000 --- a/cmd/registry-credentials-service/environments/e_production.go +++ /dev/null @@ -1,43 +0,0 @@ -package environments - -import ( - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/db_session" -) - -var _ EnvironmentImpl = &productionEnvImpl{} - -// productionEnvImpl is any deployed instance of the service through app-interface -type productionEnvImpl struct { - env *Env -} - -func (e *productionEnvImpl) OverrideDatabase(c *Database) error { - c.SessionFactory = db_session.NewProdFactory(e.env.Config.Database) - return nil -} - -func (e *productionEnvImpl) OverrideConfig(c *config.ApplicationConfig) error { - return nil -} - -func (e *productionEnvImpl) OverrideServices(s *Services) error { - return nil -} - -func (e *productionEnvImpl) OverrideHandlers(h *Handlers) error { - return nil -} - -func (e *productionEnvImpl) OverrideClients(c *Clients) error { - return nil -} - -func (e *productionEnvImpl) Flags() map[string]string { - return map[string]string{ - "v": "1", - "ocm-debug": "false", - "enable-ocm-mock": "false", - "enable-sentry": "true", - } -} diff --git a/cmd/registry-credentials-service/environments/e_unit_testing.go b/cmd/registry-credentials-service/environments/e_unit_testing.go deleted file mode 100755 index 3145610..0000000 --- a/cmd/registry-credentials-service/environments/e_unit_testing.go +++ /dev/null @@ -1,54 +0,0 @@ -package environments - -import ( - "os" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - dbmocks "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/mocks" -) - -var _ EnvironmentImpl = &unitTestingEnvImpl{} - -// unitTestingEnvImpl is configuration for unit tests using mocked database -type unitTestingEnvImpl struct { - env *Env -} - -func (e *unitTestingEnvImpl) OverrideDatabase(c *Database) error { - c.SessionFactory = dbmocks.NewMockSessionFactory() - return nil -} - -func (e *unitTestingEnvImpl) OverrideConfig(c *config.ApplicationConfig) error { - // Support a one-off env to allow enabling db debug in testing - if os.Getenv("DB_DEBUG") == "true" { - c.Database.Debug = true - } - return nil -} - -func (e *unitTestingEnvImpl) OverrideServices(s *Services) error { - return nil -} - -func (e *unitTestingEnvImpl) OverrideHandlers(h *Handlers) error { - return nil -} - -func (e *unitTestingEnvImpl) OverrideClients(c *Clients) error { - return nil -} - -func (e *unitTestingEnvImpl) Flags() map[string]string { - return map[string]string{ - "v": "0", - "logtostderr": "true", - "ocm-base-url": "https://api.integration.openshift.com", - "enable-https": "false", - "enable-metrics-https": "false", - "enable-authz": "true", - "ocm-debug": "false", - "enable-ocm-mock": "true", - "enable-sentry": "false", - } -} diff --git a/cmd/registry-credentials-service/environments/framework.go b/cmd/registry-credentials-service/environments/framework.go index 76f1f20..ecef799 100755 --- a/cmd/registry-credentials-service/environments/framework.go +++ b/cmd/registry-credentials-service/environments/framework.go @@ -1,220 +1,35 @@ package environments import ( - "fmt" - "os" - "strings" + "path/filepath" + "runtime" - "github.com/getsentry/sentry-go" - "github.com/golang/glog" - "github.com/spf13/pflag" - - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments/registry" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/client/ocm" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" + pkgenv "github.com/openshift-online/rh-trex/pkg/environments" + "github.com/openshift-online/rh-trex/pkg/trex" ) -func init() { - once.Do(func() { - environment = &Env{} - - // Create the configuration - environment.Config = config.NewApplicationConfig() - environment.Name = GetEnvironmentStrFromEnv() +type Env = pkgenv.Env +type Services = pkgenv.Services - environments = map[string]EnvironmentImpl{ - DevelopmentEnv: &devEnvImpl{environment}, - UnitTestingEnv: &unitTestingEnvImpl{environment}, - IntegrationTestingEnv: &integrationTestingEnvImpl{environment}, - ProductionEnv: &productionEnvImpl{environment}, - } +func init() { + _, thisFile, _, _ := runtime.Caller(0) + projectRoot := filepath.Join(filepath.Dir(thisFile), "..", "..", "..") + + trex.Init(trex.Config{ + ServiceName: "registry-credentials-service", + BasePath: "/api/registry-credentials-service/v1", + ErrorHref: "/api/registry-credentials-service/v1/errors/", + MetadataID: "registry-credentials-service", + ProjectRootDir: projectRoot, }) -} -// EnvironmentImpl defines a set of behaviors for an OCM environment. -// Each environment provides a set of flags for basic set/override of the environment -// and configuration functions for each component type. -type EnvironmentImpl interface { - Flags() map[string]string - OverrideConfig(c *config.ApplicationConfig) error - OverrideServices(s *Services) error - OverrideDatabase(s *Database) error - OverrideHandlers(c *Handlers) error - OverrideClients(c *Clients) error + pkgenv.NewDefaultEnvironment() } func GetEnvironmentStrFromEnv() string { - envStr, specified := os.LookupEnv(EnvironmentStringKey) - if !specified || envStr == "" { - envStr = EnvironmentDefault - } - return envStr + return pkgenv.GetEnvironmentStrFromEnv() } func Environment() *Env { - return environment -} - -// AddFlags Adds environment flags, using the environment's config struct, to the flagset 'flags' -func (e *Env) AddFlags(flags *pflag.FlagSet) error { - e.Config.AddFlags(flags) - return setConfigDefaults(flags, environments[e.Name].Flags()) -} - -// Initialize loads the environment's resources -// This should be called after the e.Config has been set appropriately though AddFlags and pasing, done elsewhere -// The environment does NOT handle flag parsing -func (e *Env) Initialize() error { - glog.Infof("Initializing %s environment", e.Name) - - envImpl, found := environments[e.Name] - if !found { - glog.Fatalf("Unknown runtime environment: %s", e.Name) - } - - if err := envImpl.OverrideConfig(e.Config); err != nil { - glog.Fatalf("Failed to configure ApplicationConfig: %s", err) - } - - messages := environment.Config.ReadFiles() - if len(messages) != 0 { - err := fmt.Errorf("unable to read configuration files:\n%s", strings.Join(messages, "\n")) - sentry.CaptureException(err) - glog.Fatalf("unable to read configuration files:\n%s", strings.Join(messages, "\n")) - } - - // each env will set db explicitly because the DB impl has a `once` init section - if err := envImpl.OverrideDatabase(&e.Database); err != nil { - glog.Fatalf("Failed to configure Database: %s", err) - } - - err := e.LoadClients() - if err != nil { - return err - } - if err := envImpl.OverrideClients(&e.Clients); err != nil { - glog.Fatalf("Failed to configure Clients: %s", err) - } - - e.LoadServices() - if err := envImpl.OverrideServices(&e.Services); err != nil { - glog.Fatalf("Failed to configure Services: %s", err) - } - - err = e.InitializeSentry() - if err != nil { - return err - } - - seedErr := e.Seed() - if seedErr != nil { - return seedErr - } - - if err := envImpl.OverrideHandlers(&e.Handlers); err != nil { - glog.Fatalf("Failed to configure Handlers: %s", err) - } - - return nil -} - -func (e *Env) Seed() *errors.ServiceError { - return nil -} - -func (e *Env) LoadServices() { - // Initialize the service registry map - e.Services.serviceRegistry = make(map[string]interface{}) - - // Auto-discovered services (no manual editing needed) - registry.LoadDiscoveredServices(&e.Services, e) -} - -func (e *Env) LoadClients() error { - var err error - - ocmConfig := ocm.Config{ - BaseURL: e.Config.OCM.BaseURL, - ClientID: e.Config.OCM.ClientID, - ClientSecret: e.Config.OCM.ClientSecret, - SelfToken: e.Config.OCM.SelfToken, - TokenURL: e.Config.OCM.TokenURL, - Debug: e.Config.OCM.Debug, - } - - // Create OCM Authz client - if e.Config.OCM.EnableMock { - glog.Infof("Using Mock OCM Authz Client") - e.Clients.OCM, err = ocm.NewClientMock(ocmConfig) - } else { - e.Clients.OCM, err = ocm.NewClient(ocmConfig) - } - if err != nil { - glog.Errorf("Unable to create OCM Authz client: %s", err.Error()) - return err - } - - return nil -} - -func (e *Env) InitializeSentry() error { - options := sentry.ClientOptions{} - - if e.Config.Sentry.Enabled { - key := e.Config.Sentry.Key - url := e.Config.Sentry.URL - project := e.Config.Sentry.Project - glog.Infof("Sentry error reporting enabled to %s on project %s", url, project) - options.Dsn = fmt.Sprintf("https://%s@%s/%s", key, url, project) - } else { - // Setting the DSN to an empty string effectively disables sentry - // See https://godoc.org/github.com/getsentry/sentry-go#ClientOptions Dsn - glog.Infof("Disabling Sentry error reporting") - options.Dsn = "" - } - - transport := sentry.NewHTTPTransport() - transport.Timeout = e.Config.Sentry.Timeout - // since sentry.HTTPTransport is asynchronous, Sentry needs a buffer to cache pending requests. - // the BufferSize is the size of the buffer. Sentry drops requests when the buffer is full: - // https://github.com/getsentry/sentry-go/blob/4f72d7725080f61e924409c8ddd008739fd4a837/transport.go#L312 - // errors in our system are relatively sparse, we don't need a large BufferSize. - transport.BufferSize = 10 - options.Transport = transport - options.Debug = e.Config.Sentry.Debug - options.AttachStacktrace = true - options.Environment = e.Name - - hostname, err := os.Hostname() - if err != nil && hostname != "" { - options.ServerName = hostname - } - // TODO figure out some way to set options.Release and options.Dist - - err = sentry.Init(options) - if err != nil { - glog.Errorf("Unable to initialize sentry integration: %s", err.Error()) - return err - } - return nil -} - -func (e *Env) Teardown() { - if e.Database.SessionFactory != nil { - if err := e.Database.SessionFactory.Close(); err != nil { - glog.Errorf("Error closing database session factory: %s", err.Error()) - } - } - e.Clients.OCM.Close() -} - -func setConfigDefaults(flags *pflag.FlagSet, defaults map[string]string) error { - for name, value := range defaults { - if err := flags.Set(name, value); err != nil { - glog.Errorf("Error setting flag %s: %v", name, err) - return err - } - } - return nil + return pkgenv.Environment() } diff --git a/cmd/registry-credentials-service/environments/framework_test.go b/cmd/registry-credentials-service/environments/framework_test.go index e0ee2d7..61ea2ee 100755 --- a/cmd/registry-credentials-service/environments/framework_test.go +++ b/cmd/registry-credentials-service/environments/framework_test.go @@ -1,17 +1,24 @@ -package environments +package environments_test import ( "os/exec" - "reflect" "testing" "github.com/spf13/pflag" + + "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" + + _ "github.com/openshift-hyperfleet/registry-credentials-service/plugins/accessTokens" + _ "github.com/openshift-hyperfleet/registry-credentials-service/plugins/registryCredentials" + _ "github.com/openshift-hyperfleet/registry-credentials-service/plugins/registrys" + _ "github.com/openshift-online/rh-trex/plugins/events" + _ "github.com/openshift-online/rh-trex/plugins/generic" ) -func BenchmarkGetDynos(b *testing.B) { +func BenchmarkGetRegistryCredentials(b *testing.B) { b.ReportAllocs() fn := func(b *testing.B) { - cmd := exec.Command("ocm", "get", "/api/registry-credential-service/v1/dinosaurs", "params='size=2'") + cmd := exec.Command("ocm", "get", "/api/registry-credentials-service/v1/registry_credentials", "params='size=2'") _, err := cmd.CombinedOutput() if err != nil { b.Errorf("ERROR %+v", err) @@ -23,7 +30,7 @@ func BenchmarkGetDynos(b *testing.B) { } func TestLoadServices(t *testing.T) { - env := Environment() + env := environments.Environment() err := env.AddFlags(pflag.CommandLine) if err != nil { t.Errorf("Unable to add flags for testing environment: %s", err.Error()) @@ -36,21 +43,17 @@ func TestLoadServices(t *testing.T) { return } - s := reflect.ValueOf(env.Services) - sType := s.Type() - - for i := 0; i < s.NumField(); i++ { - field := s.Field(i) - fieldType := sType.Field(i) - - // Skip unexported fields (lowercase first letter) - if !fieldType.IsExported() { - continue - } + expectedServices := []string{ + "Events", + "Generic", + "RegistryCredentials", + "AccessTokens", + "Registrys", + } - // Only check fields that are function types (service locators) - if field.Kind() == reflect.Func && field.IsNil() { - t.Errorf("Service locator %s is nil", fieldType.Name) + for _, name := range expectedServices { + if svc := env.Services.GetService(name); svc == nil { + t.Errorf("Service %s is nil", name) } } } diff --git a/cmd/registry-credentials-service/environments/registry/registry.go b/cmd/registry-credentials-service/environments/registry/registry.go deleted file mode 100755 index 9c64a1d..0000000 --- a/cmd/registry-credentials-service/environments/registry/registry.go +++ /dev/null @@ -1,42 +0,0 @@ -package registry - -import ( - "sync" -) - -// ServiceLocatorFunc is a function that creates a service locator -type ServiceLocatorFunc func(env interface{}) interface{} - -// ServiceRegistry holds registered services -type ServiceRegistry struct { - mu sync.RWMutex - services map[string]ServiceLocatorFunc -} - -var globalRegistry = &ServiceRegistry{ - services: make(map[string]ServiceLocatorFunc), -} - -// RegisterService registers a service with the global registry -func RegisterService(name string, locatorFunc ServiceLocatorFunc) { - globalRegistry.mu.Lock() - defer globalRegistry.mu.Unlock() - globalRegistry.services[name] = locatorFunc -} - -// ServicesInterface defines the interface for the Services struct -type ServicesInterface interface { - SetService(name string, service interface{}) -} - -// LoadDiscoveredServices loads all registered services into the Services struct -func LoadDiscoveredServices(services ServicesInterface, env interface{}) { - globalRegistry.mu.RLock() - defer globalRegistry.mu.RUnlock() - - for name, locatorFunc := range globalRegistry.services { - // Call the locator function to create the service and store it in the registry - serviceLocator := locatorFunc(env) - services.SetService(name, serviceLocator) - } -} diff --git a/cmd/registry-credentials-service/environments/types.go b/cmd/registry-credentials-service/environments/types.go deleted file mode 100755 index ddb4f24..0000000 --- a/cmd/registry-credentials-service/environments/types.go +++ /dev/null @@ -1,83 +0,0 @@ -package environments - -import ( - "sync" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/auth" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/client/ocm" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" -) - -const ( - UnitTestingEnv string = "unit_testing" - IntegrationTestingEnv string = "integration_testing" - DevelopmentEnv string = "development" - ProductionEnv string = "production" - - EnvironmentStringKey string = "OCM_ENV" - EnvironmentDefault = DevelopmentEnv -) - -type Env struct { - Name string - Services Services - Handlers Handlers - Clients Clients - Database Database - // most code relies on env.Config - Config *config.ApplicationConfig -} - -type ApplicationConfig struct { - ApplicationConfig *config.ApplicationConfig -} - -type Database struct { - SessionFactory db.SessionFactory -} - -type Handlers struct { - AuthMiddleware auth.JWTMiddleware -} - -type Services struct { - serviceRegistry map[string]interface{} - mutex sync.RWMutex -} - -func (s *Services) GetService(name string) interface{} { - s.mutex.RLock() - defer s.mutex.RUnlock() - if s.serviceRegistry == nil { - return nil - } - return s.serviceRegistry[name] -} - -func (s *Services) SetService(name string, service interface{}) { - s.mutex.Lock() - defer s.mutex.Unlock() - if s.serviceRegistry == nil { - s.serviceRegistry = make(map[string]interface{}) - } - s.serviceRegistry[name] = service -} - -type Clients struct { - OCM *ocm.Client -} - -type ConfigDefaults struct { - Server map[string]interface{} - Metrics map[string]interface{} - Database map[string]interface{} - OCM map[string]interface{} - Options map[string]interface{} -} - -var ( - environment *Env - once sync.Once - environments map[string]EnvironmentImpl -) diff --git a/cmd/registry-credentials-service/main.go b/cmd/registry-credentials-service/main.go index 38be297..4a68938 100755 --- a/cmd/registry-credentials-service/main.go +++ b/cmd/registry-credentials-service/main.go @@ -1,22 +1,16 @@ package main import ( - "flag" - "github.com/golang/glog" - "github.com/spf13/cobra" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/clone" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/migrate" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/servecmd" + localapi "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" + pkgcmd "github.com/openshift-online/rh-trex/pkg/cmd" - // Import plugins to trigger their init() functions - _ "github.com/openshift-hyperfleet/registry-credentials-service/plugins/dinosaurs" - _ "github.com/openshift-hyperfleet/registry-credentials-service/plugins/events" - _ "github.com/openshift-hyperfleet/registry-credentials-service/plugins/generic" - _ "github.com/openshift-hyperfleet/registry-credentials-service/plugins/registryCredentials" _ "github.com/openshift-hyperfleet/registry-credentials-service/plugins/accessTokens" + _ "github.com/openshift-hyperfleet/registry-credentials-service/plugins/registryCredentials" _ "github.com/openshift-hyperfleet/registry-credentials-service/plugins/registrys" + _ "github.com/openshift-online/rh-trex/plugins/events" + _ "github.com/openshift-online/rh-trex/plugins/generic" ) // nolint @@ -24,30 +18,11 @@ import ( //go:generate go-bindata -o ../../data/generated/openapi/openapi.go -pkg openapi -prefix ../../openapi/ ../../openapi func main() { - // This is needed to make `glog` believe that the flags have already been parsed, otherwise - // every log messages is prefixed by an error message stating the the flags haven't been - // parsed. - _ = flag.CommandLine.Parse([]string{}) - - //pflag.CommandLine.AddGoFlagSet(flag.CommandLine) - - // Always log to stderr by default - if err := flag.Set("logtostderr", "true"); err != nil { - glog.Infof("Unable to set logtostderr to true") - } - - rootCmd := &cobra.Command{ - Use: "registry-credential-service", - Long: "registry-credential-service serves as a template for new microservices", - } - - // All subcommands under root - migrateCmd := migrate.NewMigrateCommand() - serveCmd := servecmd.NewServeCommand() - provisionCmd := clone.NewCloneCommand() - - // Add subcommand(s) - rootCmd.AddCommand(migrateCmd, serveCmd, provisionCmd) + rootCmd := pkgcmd.NewRootCommand("registry-credentials-service", "registry-credentials-service serves as a template for new microservices") + rootCmd.AddCommand( + pkgcmd.NewMigrateCommand("registry-credentials-service"), + pkgcmd.NewServeCommand(localapi.GetOpenAPISpec), + ) if err := rootCmd.Execute(); err != nil { glog.Fatalf("error running command: %v", err) diff --git a/cmd/registry-credentials-service/migrate/cmd.go b/cmd/registry-credentials-service/migrate/cmd.go deleted file mode 100755 index 4e4a24b..0000000 --- a/cmd/registry-credentials-service/migrate/cmd.go +++ /dev/null @@ -1,41 +0,0 @@ -package migrate - -import ( - "context" - "flag" - - "github.com/golang/glog" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/db_session" - "github.com/spf13/cobra" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" -) - -var dbConfig = config.NewDatabaseConfig() - -// NewMigrateCommand migrate sub-command handles running migrations -func NewMigrateCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "migrate", - Short: "Run registry-credential-service service data migrations", - Long: "Run registry-credential-service service data migrations", - Run: runMigrate, - } - - dbConfig.AddFlags(cmd.PersistentFlags()) - cmd.PersistentFlags().AddGoFlagSet(flag.CommandLine) - return cmd -} - -func runMigrate(_ *cobra.Command, _ []string) { - err := dbConfig.ReadFiles() - if err != nil { - glog.Fatal(err) - } - - connection := db_session.NewProdFactory(dbConfig) - if err := db.Migrate(connection.New(context.Background())); err != nil { - glog.Fatal(err) - } -} diff --git a/cmd/registry-credentials-service/servecmd/cmd.go b/cmd/registry-credentials-service/servecmd/cmd.go deleted file mode 100755 index eb027c3..0000000 --- a/cmd/registry-credentials-service/servecmd/cmd.go +++ /dev/null @@ -1,54 +0,0 @@ -package servecmd - -import ( - "github.com/golang/glog" - "github.com/spf13/cobra" - - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/server" -) - -func NewServeCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "serve", - Short: "Serve the registry-credential-service", - Long: "Serve the registry-credential-service.", - Run: runServe, - } - err := environments.Environment().AddFlags(cmd.PersistentFlags()) - if err != nil { - glog.Fatalf("Unable to add environment flags to serve command: %s", err.Error()) - } - - return cmd -} - -func runServe(cmd *cobra.Command, args []string) { - err := environments.Environment().Initialize() - if err != nil { - glog.Fatalf("Unable to initialize environment: %s", err.Error()) - } - - // Run the servers - go func() { - apiserver := server.NewAPIServer() - apiserver.Start() - }() - - go func() { - metricsServer := server.NewMetricsServer() - metricsServer.Start() - }() - - go func() { - healthcheckServer := server.NewHealthCheckServer() - healthcheckServer.Start() - }() - - go func() { - controllersServer := server.NewControllersServer() - controllersServer.Start() - }() - - select {} -} diff --git a/cmd/registry-credentials-service/server/api_server.go b/cmd/registry-credentials-service/server/api_server.go deleted file mode 100755 index 898bea1..0000000 --- a/cmd/registry-credentials-service/server/api_server.go +++ /dev/null @@ -1,174 +0,0 @@ -package server - -import ( - "context" - "fmt" - "net" - "net/http" - "time" - - _ "github.com/auth0/go-jwt-middleware" - sentryhttp "github.com/getsentry/sentry-go/http" - _ "github.com/golang-jwt/jwt/v4" - "github.com/golang/glog" - gorillahandlers "github.com/gorilla/handlers" - sdk "github.com/openshift-online/ocm-sdk-go" - "github.com/openshift-online/ocm-sdk-go/authentication" - - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" -) - -type apiServer struct { - httpServer *http.Server -} - -var _ Server = &apiServer{} - -func env() *environments.Env { - return environments.Environment() -} - -func NewAPIServer() Server { - s := &apiServer{} - - mainRouter := s.routes() - - // Sentryhttp middleware performs two operations: - // 1) Attaches an instance of *sentry.Hub to the request’s context. Accessit by using the sentry.GetHubFromContext() method on the request - // NOTE this is the only way middleware, handlers, and services should be reporting to sentry, through the hub - // 2) Reports panics to the configured sentry service - if env().Config.Sentry.Enabled { - sentryhttpOptions := sentryhttp.Options{ - Repanic: true, - WaitForDelivery: false, - Timeout: env().Config.Sentry.Timeout, - } - sentryMW := sentryhttp.New(sentryhttpOptions) - mainRouter.Use(sentryMW.Handle) - } - - // referring to the router as type http.Handler allows us to add middleware via more handlers - var mainHandler http.Handler = mainRouter - - if env().Config.Server.EnableJWT { - // Create the logger for the authentication handler: - authnLogger, err := sdk.NewGlogLoggerBuilder(). - InfoV(glog.Level(1)). - DebugV(glog.Level(5)). - Build() - check(err, "Unable to create authentication logger") - - // Create the handler that verifies that tokens are valid: - mainHandler, err = authentication.NewHandler(). - Logger(authnLogger). - KeysFile(env().Config.Server.JwkCertFile). - KeysURL(env().Config.Server.JwkCertURL). - ACLFile(env().Config.Server.ACLFile). - Public("^/api/registry-credential-service/?$"). - Public("^/api/registry-credential-service/v1/?$"). - Public("^/api/registry-credential-service/v1/openapi/?$"). - Public("^/api/registry-credential-service/v1/openapi.html/?$"). - Public("^/api/registry-credential-service/v1/errors(/.*)?$"). - Next(mainHandler). - Build() - check(err, "Unable to create authentication handler") - } - - // TODO: remove all cloud.redhat.com once migration to console.redhat.com is complete - // refer to: https://issues.redhat.com/browse/RHCLOUD-14695 - mainHandler = gorillahandlers.CORS( - gorillahandlers.AllowedOrigins([]string{ - // OCM UI local development URLs - "https://qa.foo.redhat.com:1337", - "https://prod.foo.redhat.com:1337", - "https://ci.foo.redhat.com:1337", - "https://cloud.redhat.com", // TODO: remove - "https://console.redhat.com", // Production / candidate - // Staging and test environments - "https://qaprodauth.cloud.redhat.com", // TODO: remove - "https://qa.cloud.redhat.com", // TODO: remove - "https://ci.cloud.redhat.com", // TODO: remove - "https://qaprodauth.console.redhat.com", - "https://qa.console.redhat.com", - "https://ci.console.redhat.com", - "https://console.stage.redhat.com", - // API docs UI - "https://api.stage.openshift.com", - "https://api.openshift.com", - // Customer portal - "https://access.qa.redhat.com", - "https://access.stage.redhat.com", - "https://access.redhat.com", - }), - gorillahandlers.AllowedMethods([]string{ - http.MethodDelete, - http.MethodGet, - http.MethodPatch, - http.MethodPost, - }), - gorillahandlers.AllowedHeaders([]string{ - "Authorization", - "Content-Type", - }), - gorillahandlers.MaxAge(int((10 * time.Minute).Seconds())), - )(mainHandler) - - mainHandler = removeTrailingSlash(mainHandler) - - s.httpServer = &http.Server{ - Addr: env().Config.Server.BindAddress, - Handler: mainHandler, - } - - return s -} - -// Serve start the blocking call to Serve. -// Useful for breaking up ListenAndServer (Start) when you require the server to be listening before continuing -func (s apiServer) Serve(listener net.Listener) { - var err error - if env().Config.Server.EnableHTTPS { - // Check https cert and key path path - if env().Config.Server.HTTPSCertFile == "" || env().Config.Server.HTTPSKeyFile == "" { - check( - fmt.Errorf("unspecified required --https-cert-file, --https-key-file"), - "Can't start https server", - ) - } - - // Serve with TLS - glog.Infof("Serving with TLS at %s", env().Config.Server.BindAddress) - err = s.httpServer.ServeTLS(listener, env().Config.Server.HTTPSCertFile, env().Config.Server.HTTPSKeyFile) - } else { - glog.Infof("Serving without TLS at %s", env().Config.Server.BindAddress) - err = s.httpServer.Serve(listener) - } - - // Web server terminated. - check(err, "Web server terminated with errors") - glog.Info("Web server terminated") -} - -// Listen only start the listener, not the server. -// Useful for breaking up ListenAndServer (Start) when you require the server to be listening before continuing -func (s apiServer) Listen() (listener net.Listener, err error) { - return net.Listen("tcp", env().Config.Server.BindAddress) -} - -// Start listening on the configured port and start the server. This is a convenience wrapper for Listen() and Serve(listener Listener) -func (s apiServer) Start() { - listener, err := s.Listen() - if err != nil { - glog.Fatalf("Unable to start API server: %s", err) - } - s.Serve(listener) - - // after the server exits but before the application terminates - // we need to explicitly close Go's sql connection pool. - // this needs to be called *exactly* once during an app's lifetime. - env().Database.SessionFactory.Close() -} - -func (s apiServer) Stop() error { - return s.httpServer.Shutdown(context.Background()) -} diff --git a/cmd/registry-credentials-service/server/controllers.go b/cmd/registry-credentials-service/server/controllers.go deleted file mode 100755 index d0241ba..0000000 --- a/cmd/registry-credentials-service/server/controllers.go +++ /dev/null @@ -1,57 +0,0 @@ -package server - -import ( - "context" - - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/controllers" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/plugins/events" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" -) - -type ControllerRegistrationFunc func(manager *controllers.KindControllerManager, services *environments.Services) - -var controllerRegistry = make(map[string]ControllerRegistrationFunc) - -func RegisterController(name string, registrationFunc ControllerRegistrationFunc) { - controllerRegistry[name] = registrationFunc -} - -func LoadDiscoveredControllers(manager *controllers.KindControllerManager, services *environments.Services) { - for name, registrationFunc := range controllerRegistry { - registrationFunc(manager, services) - _ = name // prevent unused variable warning - } -} - -func NewControllersServer() *ControllersServer { - - s := &ControllersServer{ - KindControllerManager: controllers.NewKindControllerManager( - db.NewAdvisoryLockFactory(env().Database.SessionFactory), - events.Service(&env().Services), - ), - } - - // Auto-discovered controllers (no manual editing needed) - LoadDiscoveredControllers(s.KindControllerManager, &env().Services) - - return s -} - -type ControllersServer struct { - KindControllerManager *controllers.KindControllerManager - DB db.SessionFactory -} - -// Start is a blocking call that starts this controller server -func (s ControllersServer) Start() { - log := logger.NewOCMLogger(context.Background()) - - log.Infof("Kind controller listening for events") - - // blocking call - env().Database.SessionFactory.NewListener(context.Background(), "events", s.KindControllerManager.Handle) -} diff --git a/cmd/registry-credentials-service/server/healthcheck_server.go b/cmd/registry-credentials-service/server/healthcheck_server.go deleted file mode 100755 index f677234..0000000 --- a/cmd/registry-credentials-service/server/healthcheck_server.go +++ /dev/null @@ -1,82 +0,0 @@ -package server - -import ( - "context" - "fmt" - "net" - "net/http" - - health "github.com/docker/go-healthcheck" - "github.com/golang/glog" - "github.com/gorilla/mux" -) - -var ( - updater = health.NewStatusUpdater() -) - -var _ Server = &healthCheckServer{} - -type healthCheckServer struct { - httpServer *http.Server -} - -func NewHealthCheckServer() *healthCheckServer { - router := mux.NewRouter() - health.DefaultRegistry = health.NewRegistry() - health.Register("maintenance_status", updater) - router.HandleFunc("/healthcheck", health.StatusHandler).Methods(http.MethodGet) - router.HandleFunc("/healthcheck/down", downHandler).Methods(http.MethodPost) - router.HandleFunc("/healthcheck/up", upHandler).Methods(http.MethodPost) - - srv := &http.Server{ - Handler: router, - Addr: env().Config.HealthCheck.BindAddress, - } - - return &healthCheckServer{ - httpServer: srv, - } -} - -func (s healthCheckServer) Start() { - var err error - if env().Config.HealthCheck.EnableHTTPS { - if env().Config.Server.HTTPSCertFile == "" || env().Config.Server.HTTPSKeyFile == "" { - check( - fmt.Errorf("unspecified required --https-cert-file, --https-key-file"), - "Can't start https server", - ) - } - - // Serve with TLS - glog.Infof("Serving HealthCheck with TLS at %s", env().Config.HealthCheck.BindAddress) - err = s.httpServer.ListenAndServeTLS(env().Config.Server.HTTPSCertFile, env().Config.Server.HTTPSKeyFile) - } else { - glog.Infof("Serving HealthCheck without TLS at %s", env().Config.HealthCheck.BindAddress) - err = s.httpServer.ListenAndServe() - } - check(err, "HealthCheck server terminated with errors") - glog.Infof("HealthCheck server terminated") -} - -func (s healthCheckServer) Stop() error { - return s.httpServer.Shutdown(context.Background()) -} - -// Listen Unimplemented -func (s healthCheckServer) Listen() (listener net.Listener, err error) { - return nil, nil -} - -// Serve Unimplemented -func (s healthCheckServer) Serve(listener net.Listener) { -} - -func upHandler(w http.ResponseWriter, r *http.Request) { - updater.Update(nil) -} - -func downHandler(w http.ResponseWriter, r *http.Request) { - updater.Update(fmt.Errorf("maintenance mode")) -} diff --git a/cmd/registry-credentials-service/server/logging/formatter.go b/cmd/registry-credentials-service/server/logging/formatter.go deleted file mode 100755 index 547bcef..0000000 --- a/cmd/registry-credentials-service/server/logging/formatter.go +++ /dev/null @@ -1,8 +0,0 @@ -package logging - -import "net/http" - -type LogFormatter interface { - FormatRequestLog(request *http.Request) (string, error) - FormatResponseLog(responseInfo *ResponseInfo) (string, error) -} diff --git a/cmd/registry-credentials-service/server/logging/formatter_json.go b/cmd/registry-credentials-service/server/logging/formatter_json.go deleted file mode 100755 index 9d0062c..0000000 --- a/cmd/registry-credentials-service/server/logging/formatter_json.go +++ /dev/null @@ -1,62 +0,0 @@ -package logging - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/golang/glog" -) - -func NewJSONLogFormatter() *jsonLogFormatter { - return &jsonLogFormatter{} -} - -type jsonLogFormatter struct{} - -var _ LogFormatter = &jsonLogFormatter{} - -func (f *jsonLogFormatter) FormatRequestLog(r *http.Request) (string, error) { - jsonlog := jsonRequestLog{ - Method: r.Method, - RequestURI: r.RequestURI, - RemoteAddr: r.RemoteAddr, - } - if glog.V(10) { - jsonlog.Header = r.Header - jsonlog.Body = r.Body - } - - log, err := json.Marshal(jsonlog) - if err != nil { - return "", err - } - return string(log[:]), nil -} - -func (f *jsonLogFormatter) FormatResponseLog(info *ResponseInfo) (string, error) { - jsonlog := jsonResponseLog{Header: nil, Status: info.Status, Elapsed: info.Elapsed} - if glog.V(10) { - jsonlog.Body = string(info.Body[:]) - } - log, err := json.Marshal(jsonlog) - if err != nil { - return "", err - } - return string(log[:]), nil -} - -type jsonRequestLog struct { - Method string `json:"request_method"` - RequestURI string `json:"request_url"` - Header http.Header `json:"request_header,omitempty"` - Body io.ReadCloser `json:"request_body,omitempty"` - RemoteAddr string `json:"request_remote_ip,omitempty"` -} - -type jsonResponseLog struct { - Header http.Header `json:"response_header,omitempty"` - Status int `json:"response_status,omitempty"` - Body string `json:"response_body,omitempty"` - Elapsed string `json:"elapsed,omitempty"` -} diff --git a/cmd/registry-credentials-service/server/logging/logging.go b/cmd/registry-credentials-service/server/logging/logging.go deleted file mode 100755 index 1c889df..0000000 --- a/cmd/registry-credentials-service/server/logging/logging.go +++ /dev/null @@ -1,3 +0,0 @@ -package logging - -const Threshold int32 = 1 diff --git a/cmd/registry-credentials-service/server/logging/request_logging_middleware.go b/cmd/registry-credentials-service/server/logging/request_logging_middleware.go deleted file mode 100755 index 22545d9..0000000 --- a/cmd/registry-credentials-service/server/logging/request_logging_middleware.go +++ /dev/null @@ -1,34 +0,0 @@ -package logging - -import ( - "net/http" - "strings" - "time" -) - -func RequestLoggingMiddleware(handler http.Handler) http.Handler { - return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - path := strings.TrimSuffix(request.URL.Path, "/") - doLog := true - - // these contribute greatly to log spam but are not useful or meaningful. - // consider a list/map of URLs should this grow in the future. - if path == "/api/registry-credential-service" { - doLog = false - } - - loggingWriter := NewLoggingWriter(writer, request, NewJSONLogFormatter()) - - if doLog { - loggingWriter.log(loggingWriter.prepareRequestLog()) - } - - before := time.Now() - handler.ServeHTTP(loggingWriter, request) - elapsed := time.Since(before).String() - - if doLog { - loggingWriter.log(loggingWriter.prepareResponseLog(elapsed)) - } - }) -} diff --git a/cmd/registry-credentials-service/server/logging/responseinfo.go b/cmd/registry-credentials-service/server/logging/responseinfo.go deleted file mode 100755 index 089a120..0000000 --- a/cmd/registry-credentials-service/server/logging/responseinfo.go +++ /dev/null @@ -1,10 +0,0 @@ -package logging - -import "net/http" - -type ResponseInfo struct { - Header http.Header `json:"response_header,omitempty"` - Body []byte `json:"response_body,omitempty"` - Status int `json:"response_status,omitempty"` - Elapsed string `json:"elapsed,omitempty"` -} diff --git a/cmd/registry-credentials-service/server/logging/writer.go b/cmd/registry-credentials-service/server/logging/writer.go deleted file mode 100755 index c1467f0..0000000 --- a/cmd/registry-credentials-service/server/logging/writer.go +++ /dev/null @@ -1,54 +0,0 @@ -package logging - -import ( - "net/http" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" -) - -func NewLoggingWriter(w http.ResponseWriter, r *http.Request, f LogFormatter) *loggingWriter { - return &loggingWriter{ResponseWriter: w, request: r, formatter: f} -} - -type loggingWriter struct { - http.ResponseWriter - request *http.Request - formatter LogFormatter - responseStatus int - responseBody []byte -} - -func (writer *loggingWriter) Write(body []byte) (int, error) { - writer.responseBody = body - return writer.ResponseWriter.Write(body) -} - -func (writer *loggingWriter) WriteHeader(status int) { - writer.responseStatus = status - writer.ResponseWriter.WriteHeader(status) -} - -func (writer *loggingWriter) log(logMsg string, err error) { - log := logger.NewOCMLogger(writer.request.Context()) - switch err { - case nil: - log.V(Threshold).Infof(logMsg) - default: - log.Extra("error", err.Error()).Error("Unable to format request/response for log.") - } -} - -func (writer *loggingWriter) prepareRequestLog() (string, error) { - return writer.formatter.FormatRequestLog(writer.request) -} - -func (writer *loggingWriter) prepareResponseLog(elapsed string) (string, error) { - info := &ResponseInfo{ - Header: writer.ResponseWriter.Header(), - Body: writer.responseBody, - Status: writer.responseStatus, - Elapsed: elapsed, - } - - return writer.formatter.FormatResponseLog(info) -} diff --git a/cmd/registry-credentials-service/server/metrics_middleware.go b/cmd/registry-credentials-service/server/metrics_middleware.go deleted file mode 100755 index 448593f..0000000 --- a/cmd/registry-credentials-service/server/metrics_middleware.go +++ /dev/null @@ -1,197 +0,0 @@ -/* -Copyright (c) 2019 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// This file contains an HTTP middleware that generates metrics about API requests: -// -// api_inbound_request_count - Number of requests served. -// api_inbound_request_duration_sum - Total time to process requests, in seconds. -// api_inbound_request_duration_count - Total number of requests measured. -// api_inbound_request_duration_bucket - Number of requests that processed in less than a given time. -// -// The duration buckets metrics contain an `le` label that indicates the upper. For example if the -// `le` label is `1` then the value will be the number of requests that were processed in less than -// one second. -// -// All the metrics have the following labels: -// -// method - Name of the HTTP method, for example GET or POST. -// path - Request path, for example /api/clusters_mgmt/v1/clusters. -// code - HTTP response code, for example 200 or 500. -// -// To calculate the average request duration during the last 10 minutes, for example, use a -// Prometheus expression like this: -// -// rate(api_inbound_request_duration_sum[10m]) / rate(api_inbound_request_duration_count[10m]) -// -// In order to reduce the cardinality of the metrics the path label is modified to remove the -// identifiers of the objects. For example, if the original path is .../clusters/123 then it will -// be replaced by .../clusters/-, and the values will be accumulated. The line returned by the -// metrics server will be like this: -// -// api_inbound_request_count{code="200",method="GET",path="/api/clusters_mgmt/v1/clusters/-"} 56 -// -// The meaning of that is that there were a total of 56 requests to get specific clusters, -// independently of the specific identifier of the cluster. - -package server - -import ( - "net/http" - "regexp" - "strconv" - "time" - - "github.com/gorilla/mux" - "github.com/prometheus/client_golang/prometheus" -) - -// MetricsMiddleware creates a new handler that collects metrics for the requests processed by the -// given handler. -func MetricsMiddleware(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Wrap the origial response writer with one that will allow as to get the response - // status code: - wrapper := &metricsResponseWrapper{ - wrapped: w, - } - - // Call the next handler measuring the time that it takes: - before := time.Now() - handler.ServeHTTP(wrapper, r) - elapsed := time.Since(before) - - // In order to reduce the cardinality of the metrics we need to remove from the - // request path all the object identifiers: - path := "/" + PathVarSub - route := mux.CurrentRoute(r) - if route != nil { - template, err := route.GetPathTemplate() - if err == nil { - path = metricsPathVarRE.ReplaceAllString(template, PathVarSub) - } - } - - // Create the set of labels that we will add to all the requests: - labels := prometheus.Labels{ - metricsMethodLabel: r.Method, - metricsPathLabel: path, - metricsCodeLabel: strconv.Itoa(wrapper.code), - } - - // Update the metric containing the number of requests: - requestCountMetric.With(labels).Inc() - - // Update the metrics containing the response duration: - requestDurationMetric.With(labels).Observe(elapsed.Seconds()) - }) -} - -// ResetMetricCollectors resets all prometheus collectors -func ResetMetricCollectors() { - requestCountMetric.Reset() - requestDurationMetric.Reset() -} - -// Regular expression used to remove variables from route path templates: -var metricsPathVarRE = regexp.MustCompile(`{[^}]*}`) - -// PathVarSub replaces path variables to a same character -var PathVarSub = "-" - -// Subsystem used to define the metrics: -const metricsSubsystem = "api_inbound" - -// Names of the labels added to metrics: -const ( - metricsMethodLabel = "method" - metricsPathLabel = "path" - metricsCodeLabel = "code" -) - -// MetricsLabels - Array of labels added to metrics: -var MetricsLabels = []string{ - metricsMethodLabel, - metricsPathLabel, - metricsCodeLabel, -} - -// Names of the metrics: -const ( - requestCount = "request_count" - requestDuration = "request_duration" -) - -// MetricsNames - Array of Names of the metrics: -var MetricsNames = []string{ - requestCount, - requestDuration, -} - -// Description of the requests count metric: -var requestCountMetric = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Subsystem: metricsSubsystem, - Name: requestCount, - Help: "Number of requests served.", - }, - MetricsLabels, -) - -// Description of the request duration metric: -var requestDurationMetric = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Subsystem: metricsSubsystem, - Name: requestDuration, - Help: "Request duration in seconds.", - Buckets: []float64{ - 0.1, - 1.0, - 10.0, - 30.0, - }, - }, - MetricsLabels, -) - -// metricsResponseWrapper is an extension of the HTTP response writer that remembers the status code, -// so that we can add to metrics after the response is sent to the client. -type metricsResponseWrapper struct { - wrapped http.ResponseWriter - code int -} - -func (w *metricsResponseWrapper) Header() http.Header { - return w.wrapped.Header() -} - -func (w *metricsResponseWrapper) Write(b []byte) (n int, err error) { - if w.code == 0 { - w.code = http.StatusOK - } - n, err = w.wrapped.Write(b) - return -} - -func (w *metricsResponseWrapper) WriteHeader(code int) { - w.code = code - w.wrapped.WriteHeader(code) -} - -func init() { - // Register the metrics: - prometheus.MustRegister(requestCountMetric) - prometheus.MustRegister(requestDurationMetric) -} diff --git a/cmd/registry-credentials-service/server/metrics_server.go b/cmd/registry-credentials-service/server/metrics_server.go deleted file mode 100755 index 60240ac..0000000 --- a/cmd/registry-credentials-service/server/metrics_server.go +++ /dev/null @@ -1,71 +0,0 @@ -package server - -import ( - "context" - "fmt" - "net" - "net/http" - - "github.com/gorilla/mux" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/handlers" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" -) - -func NewMetricsServer() Server { - mainRouter := mux.NewRouter() - mainRouter.NotFoundHandler = http.HandlerFunc(api.SendNotFound) - - // metrics endpoint - prometheusMetricsHandler := handlers.NewPrometheusMetricsHandler() - mainRouter.Handle("/metrics", prometheusMetricsHandler.Handler()) - - var mainHandler http.Handler = mainRouter - - s := &metricsServer{} - s.httpServer = &http.Server{ - Addr: env().Config.Metrics.BindAddress, - Handler: mainHandler, - } - return s -} - -type metricsServer struct { - httpServer *http.Server -} - -var _ Server = &metricsServer{} - -func (s metricsServer) Listen() (listener net.Listener, err error) { - return nil, nil -} - -func (s metricsServer) Serve(listener net.Listener) { -} - -func (s metricsServer) Start() { - log := logger.NewOCMLogger(context.Background()) - var err error - if env().Config.Metrics.EnableHTTPS { - if env().Config.Server.HTTPSCertFile == "" || env().Config.Server.HTTPSKeyFile == "" { - check( - fmt.Errorf("unspecified required --https-cert-file, --https-key-file"), - "Can't start https server", - ) - } - - // Serve with TLS - log.Infof("Serving Metrics with TLS at %s", env().Config.Server.BindAddress) - err = s.httpServer.ListenAndServeTLS(env().Config.Server.HTTPSCertFile, env().Config.Server.HTTPSKeyFile) - } else { - log.Infof("Serving Metrics without TLS at %s", env().Config.Metrics.BindAddress) - err = s.httpServer.ListenAndServe() - } - check(err, "Metrics server terminated with errors") - log.Infof("Metrics server terminated") -} - -func (s metricsServer) Stop() error { - return s.httpServer.Shutdown(context.Background()) -} diff --git a/cmd/registry-credentials-service/server/routes.go b/cmd/registry-credentials-service/server/routes.go deleted file mode 100755 index e4929a5..0000000 --- a/cmd/registry-credentials-service/server/routes.go +++ /dev/null @@ -1,99 +0,0 @@ -package server - -import ( - "fmt" - "net/http" - - gorillahandlers "github.com/gorilla/handlers" - "github.com/gorilla/mux" - - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/server/logging" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/auth" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/handlers" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" -) - -type ServicesInterface interface { - GetService(name string) interface{} -} - -type RouteRegistrationFunc func(apiV1Router *mux.Router, services ServicesInterface, authMiddleware auth.JWTMiddleware, authzMiddleware auth.AuthorizationMiddleware) - -var routeRegistry = make(map[string]RouteRegistrationFunc) - -func RegisterRoutes(name string, registrationFunc RouteRegistrationFunc) { - routeRegistry[name] = registrationFunc -} - -func LoadDiscoveredRoutes(apiV1Router *mux.Router, services ServicesInterface, authMiddleware auth.JWTMiddleware, authzMiddleware auth.AuthorizationMiddleware) { - for name, registrationFunc := range routeRegistry { - registrationFunc(apiV1Router, services, authMiddleware, authzMiddleware) - _ = name // prevent unused variable warning - } -} - -func (s *apiServer) routes() *mux.Router { - services := &env().Services - - metadataHandler := handlers.NewMetadataHandler() - - var authMiddleware auth.JWTMiddleware - authMiddleware = &auth.MiddlewareMock{} - if env().Config.Server.EnableJWT { - var err error - authMiddleware, err = auth.NewAuthMiddleware() - check(err, "Unable to create auth middleware") - } - if authMiddleware == nil { - check(fmt.Errorf("auth middleware is nil"), "Unable to create auth middleware: missing middleware") - } - - authzMiddleware := auth.NewAuthzMiddlewareMock() - if env().Config.Server.EnableAuthz { - // TODO: authzMiddleware, err = auth.NewAuthzMiddleware() - // check(err, "Unable to create authz middleware") - } - - // mainRouter is top level "/" - mainRouter := mux.NewRouter() - mainRouter.NotFoundHandler = http.HandlerFunc(api.SendNotFound) - - // Operation ID middleware sets a relatively unique operation ID in the context of each request for debugging purposes - mainRouter.Use(logger.OperationIDMiddleware) - - // Request logging middleware logs pertinent information about the request and response - mainRouter.Use(logging.RequestLoggingMiddleware) - - // /api/registry-credential-service - apiRouter := mainRouter.PathPrefix("/api/registry-credential-service").Subrouter() - apiRouter.HandleFunc("", metadataHandler.Get).Methods(http.MethodGet) - - // /api/registry-credential-service/v1 - apiV1Router := apiRouter.PathPrefix("/v1").Subrouter() - - // /api/registry-credential-service/v1/openapi - openapiHandler, err := handlers.NewOpenAPIHandler() - check(err, "Unable to create OpenAPI handler") - apiV1Router.HandleFunc("/openapi.html", openapiHandler.GetOpenAPIUI).Methods(http.MethodGet) - apiV1Router.HandleFunc("/openapi", openapiHandler.GetOpenAPI).Methods(http.MethodGet) - registerApiMiddleware(apiV1Router) - - // Auto-discovered routes (no manual editing needed) - LoadDiscoveredRoutes(apiV1Router, services, authMiddleware, authzMiddleware) - - return mainRouter -} - -func registerApiMiddleware(router *mux.Router) { - router.Use(MetricsMiddleware) - - router.Use( - func(next http.Handler) http.Handler { - return db.TransactionMiddleware(next, env().Database.SessionFactory) - }, - ) - - router.Use(gorillahandlers.CompressHandler) -} diff --git a/cmd/registry-credentials-service/server/server.go b/cmd/registry-credentials-service/server/server.go deleted file mode 100755 index 710b025..0000000 --- a/cmd/registry-credentials-service/server/server.go +++ /dev/null @@ -1,37 +0,0 @@ -package server - -import ( - "net" - "net/http" - "os" - "strings" - - "github.com/getsentry/sentry-go" - "github.com/golang/glog" - - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" -) - -type Server interface { - Start() - Stop() error - Listen() (net.Listener, error) - Serve(net.Listener) -} - -func removeTrailingSlash(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r.URL.Path = strings.TrimSuffix(r.URL.Path, "/") - next.ServeHTTP(w, r) - }) -} - -// Exit on error -func check(err error, msg string) { - if err != nil && err != http.ErrServerClosed { - glog.Errorf("%s: %s", msg, err) - sentry.CaptureException(err) - sentry.Flush(environments.Environment().Config.Sentry.Timeout) - os.Exit(1) - } -} diff --git a/demos/1752581357_circus_demo.md b/demos/1752581357_circus_demo.md deleted file mode 100755 index 03a6c0f..0000000 --- a/demos/1752581357_circus_demo.md +++ /dev/null @@ -1,181 +0,0 @@ -# Instant API™ Demo Results: Circus Management System - -**Demo Date**: 2025-07-15 -**Project**: Circus Management API -**Epoch**: 1752581357 -**Demo Status**: ✅ **SUCCESSFUL** - -## Executive Summary - -Successfully demonstrated the complete **Instant API™** workflow by creating a fully functional Circus Management API from business concept to working REST endpoints with database integration in under 30 minutes. - -## Project Overview - -**Business Domain**: Circus Operations Management -**Generated Project**: `~/projects/src/github.com/openshift-online/circus` -**Database**: PostgreSQL (circus/circus/foobar-bizz-buzz) -**API Base**: `/api/circus/v1/` - -## Generated Entities - -### 🎭 **Performer Entity** -- **Business Fields**: Name, Specialty, ExperienceYears, Salary, Status, HireDate, ContactInfo -- **REST Endpoints**: - - `GET /api/circus/v1/performers` - List all performers - - `POST /api/circus/v1/performers` - Create new performer - - `GET /api/circus/v1/performers/{id}` - Get specific performer - - `PATCH /api/circus/v1/performers/{id}` - Update performer - - `DELETE /api/circus/v1/performers/{id}` - Remove performer - -### 🎪 **Act Entity** -- **Business Fields**: Name, Description, DurationMinutes, DifficultyLevel, EquipmentRequired, SafetyRating -- **REST Endpoints**: - - `GET /api/circus/v1/acts` - List all acts - - `POST /api/circus/v1/acts` - Create new act - - `GET /api/circus/v1/acts/{id}` - Get specific act - - `PATCH /api/circus/v1/acts/{id}` - Update act - - `DELETE /api/circus/v1/acts/{id}` - Remove act - -### 🎟️ **Show Entity** -- **Business Fields**: Title, Description, ShowDate, StartTime, DurationMinutes, TicketPrice, MaxAudience, Status, VenueID -- **REST Endpoints**: - - `GET /api/circus/v1/shows` - List all shows - - `POST /api/circus/v1/shows` - Create new show - - `GET /api/circus/v1/shows/{id}` - Get specific show - - `PATCH /api/circus/v1/shows/{id}` - Update show - - `DELETE /api/circus/v1/shows/{id}` - Cancel show - -### 🏟️ **Venue Entity** -- **Business Fields**: Name, Address, City, Capacity, VenueType, Facilities, ContactInfo -- **REST Endpoints**: - - `GET /api/circus/v1/venues` - List all venues - - `POST /api/circus/v1/venues` - Create new venue - - `GET /api/circus/v1/venues/{id}` - Get specific venue - - `PATCH /api/circus/v1/venues/{id}` - Update venue - - `DELETE /api/circus/v1/venues/{id}` - Remove venue - -## Phase Execution Results - -### ✅ **Phase 1: Project Definition** -- **Status**: COMPLETED -- **Duration**: ~5 minutes -- **Deliverables**: - - Business domain concept: Circus Operations Management - - Complete UML entity relationship diagram - - MODELS.demo.md with comprehensive business model - -### ✅ **Phase 2: Project Cloning & Setup** -- **Status**: COMPLETED -- **Duration**: ~10 minutes -- **Deliverables**: - - Cloned registry-credentials-service project to `~/projects/src/github.com/openshift-online/circus` - - Applied CLONING.md post-clone fixes - - PostgreSQL database container running (psql-circus) - - Successfully building circus binary - -### ✅ **Phase 3: Model Generation** -- **Status**: COMPLETED -- **Duration**: ~10 minutes -- **Deliverables**: - - Generated 4 complete entities using registry-credentials-service generator - - Fixed generator service locator registration bug - - Added business fields from UML model to all entities - - OpenAPI specifications generated successfully - - Database migration completed with business schema - -### ✅ **Phase 4: Testing & Verification** -- **Status**: COMPLETED -- **Duration**: ~5 minutes -- **Deliverables**: - - Binary builds successfully (`make binary`) - - Database migration successful - - OpenAPI generation creates complete API documentation - - Service locator pattern working correctly - -## Issues Encountered & Resolutions - -### 🔧 **Issue 1: Generator Service Locator Bug** -- **Problem**: registry-credentials-service generator uses overly broad pattern matching that corrupts all struct definitions -- **Impact**: Service locator fields inappropriately added to ApplicationConfig, Database, Handlers, Clients structs -- **Resolution**: Manual cleanup of types.go and framework.go files to remove erroneous service locator fields -- **Status**: RESOLVED - -### 🔧 **Issue 2: OpenAPI Generation Required Before Migration** -- **Problem**: Build failures when trying to run migration without OpenAPI generation -- **Impact**: Missing openapi client packages cause compilation errors -- **Resolution**: Added `make generate` step before migration in workflow -- **Status**: RESOLVED - Updated INSTANTAPI.md with proper sequence - -### 🔧 **Issue 3: Integration Test API References** -- **Problem**: Integration tests reference original ApiRegistryCredentialServiceV1* methods instead of ApiCircusV1* methods -- **Impact**: Integration tests fail to build after clone -- **Resolution**: Updated CLONING.md with instructions for fixing API references -- **Status**: DOCUMENTED - Manual fix required for integration tests - -## Technical Achievements - -### 🏗️ **Architecture** -- **Service Locator Pattern**: Properly implemented dependency injection -- **GORM Integration**: Full ORM with PostgreSQL database -- **OpenAPI 3.0 Specification**: Complete API documentation generation -- **RESTful Design**: Standard HTTP methods and status codes - -### 🛠️ **Build System** -- **Make-based Build**: Consistent build targets across projects -- **Container Integration**: PostgreSQL database in containers -- **Go Modules**: Proper dependency management -- **Code Generation**: Automated OpenAPI client generation - -### 📊 **Data Model** -- **Entity Relationships**: Proper foreign key relationships between entities -- **Business Rules**: Comprehensive field validation and constraints -- **Indexing Strategy**: Optimized database indexes for performance -- **Soft Deletes**: GORM soft delete pattern implemented - -## Performance Metrics - -- **Total Demo Time**: ~30 minutes -- **Entities Generated**: 4 complete entities -- **API Endpoints**: 20 REST endpoints (5 per entity) -- **Business Fields**: 25 business-specific fields across all entities -- **Database Tables**: 4 tables with complete business schema -- **Build Success Rate**: 100% (after OpenAPI generation) - -## Instant API™ Workflow Assessment - -### 🎯 **Strengths** -1. **Rapid Development**: From concept to working API in under 30 minutes -2. **Complete Feature Set**: Full CRUD operations with database integration -3. **Professional Quality**: Production-ready code with proper patterns -4. **Comprehensive Documentation**: Auto-generated OpenAPI specifications -5. **Extensible Architecture**: Service locator pattern enables easy extension - -### 🔍 **Areas for Improvement** -1. **Generator Pattern Matching**: Fix overly broad pattern matching in service locator registration -2. **Post-Clone Automation**: Automate integration test API reference updates -3. **Build Dependencies**: Clearer documentation of OpenAPI generation requirement -4. **Testing Integration**: Streamline integration test workflow - -### 📈 **Business Value** -- **Development Speed**: 10x faster than traditional API development -- **Consistency**: Standardized patterns across all generated APIs -- **Quality**: Professional-grade code with proper architecture -- **Maintainability**: Clear separation of concerns and dependency injection - -## Conclusion - -The **Instant API™** demonstration successfully proved the capability to rapidly generate production-quality REST APIs from business domain concepts. The Circus Management API showcases the complete workflow from UML modeling to functional endpoints with database integration. - -**Key Success Factors**: -- Comprehensive business model design (MODELS.demo.md) -- Systematic phase-based approach (INSTANTAPI.md) -- Proper issue resolution and documentation (CLONING.md) -- Complete verification and testing procedures - -**Recommendation**: Deploy Instant API™ for rapid prototyping and MVP development in enterprise environments. - ---- - -**Demo conducted by**: Claude Code -**Instant API™ Version**: registry-credentials-service Template System -**Next Steps**: Address generator pattern matching improvements and automate post-clone fixes \ No newline at end of file diff --git a/demos/1752594736_k8s_demo.md b/demos/1752594736_k8s_demo.md deleted file mode 100755 index d9478f9..0000000 --- a/demos/1752594736_k8s_demo.md +++ /dev/null @@ -1,196 +0,0 @@ -# Instant API™ Demo Results: Kubernetes Management System - -**Demo Date**: 2025-07-15 -**Project**: Kubernetes Management API -**Epoch**: 1752594736 -**Demo Status**: ❌ **FAILED** - -## Executive Summary - -**FAILED**: Integration tests do not pass due to incomplete database migrations. While the complete **Instant API™** workflow was executed and all entities were generated successfully, the registry-credentials-service generator creates incomplete migration files that only include the base Model struct without business fields, causing database column errors during testing. - -## Project Overview - -**Business Domain**: Kubernetes Cluster Management -**Generated Project**: `~/projects/src/github.com/openshift-online/k8s` -**Database**: PostgreSQL (k8s/k8s/foobar-bizz-buzz) -**API Base**: `/api/k8s/v1/` - -## Generated Entities - -### 🏗️ **Cluster Entity** -- **Business Fields**: Name, Provider, Version, Endpoint, Status, Region, NodeCount, NodeInstanceType, Metadata -- **REST Endpoints**: - - `GET /api/k8s/v1/clusters` - List all clusters - - `POST /api/k8s/v1/clusters` - Create new cluster - - `GET /api/k8s/v1/clusters/{id}` - Get specific cluster - - `PATCH /api/k8s/v1/clusters/{id}` - Update cluster - - `DELETE /api/k8s/v1/clusters/{id}` - Delete cluster - -### 🏠 **Namespace Entity** -- **Business Fields**: Name, ClusterID, Status, ResourceQuotas, Labels, Annotations -- **REST Endpoints**: - - `GET /api/k8s/v1/namespaces` - List all namespaces - - `POST /api/k8s/v1/namespaces` - Create new namespace - - `GET /api/k8s/v1/namespaces/{id}` - Get specific namespace - - `PATCH /api/k8s/v1/namespaces/{id}` - Update namespace - - `DELETE /api/k8s/v1/namespaces/{id}` - Delete namespace - -### 📦 **Application Entity** -- **Business Fields**: Name, NamespaceID, ClusterID, Image, Version, Status, EnvironmentVariables, Configuration, Replicas, HealthCheckPath -- **REST Endpoints**: - - `GET /api/k8s/v1/applications` - List all applications - - `POST /api/k8s/v1/applications` - Create new application - - `GET /api/k8s/v1/applications/{id}` - Get specific application - - `PATCH /api/k8s/v1/applications/{id}` - Update application - - `DELETE /api/k8s/v1/applications/{id}` - Delete application - -### 🚀 **Deployment Entity** -- **Business Fields**: Name, ApplicationID, Image, Version, Status, Replicas, AvailableReplicas, ReadyReplicas, Strategy, PodTemplate -- **REST Endpoints**: - - `GET /api/k8s/v1/deployments` - List all deployments - - `POST /api/k8s/v1/deployments` - Create new deployment - - `GET /api/k8s/v1/deployments/{id}` - Get specific deployment - - `PATCH /api/k8s/v1/deployments/{id}` - Update deployment - - `DELETE /api/k8s/v1/deployments/{id}` - Delete deployment - -### 🌐 **Service Entity** -- **Business Fields**: Name, ApplicationID, NamespaceID, ServiceType, Selector, Ports, ClusterIP, ExternalIP -- **REST Endpoints**: - - `GET /api/k8s/v1/services` - List all services - - `POST /api/k8s/v1/services` - Create new service - - `GET /api/k8s/v1/services/{id}` - Get specific service - - `PATCH /api/k8s/v1/services/{id}` - Update service - - `DELETE /api/k8s/v1/services/{id}` - Delete service - -## Phase Execution Results - -### ✅ **Phase 1: Project Definition** -- **Status**: COMPLETED -- **Duration**: ~3 minutes -- **Deliverables**: - - Business domain concept: Kubernetes Cluster Management - - Complete UML entity relationship diagram with 5 entities - - MODELS.demo.md with comprehensive Kubernetes business model - -### ✅ **Phase 2: Project Cloning & Setup** -- **Status**: COMPLETED -- **Duration**: ~8 minutes -- **Deliverables**: - - Cloned registry-credentials-service project to `~/projects/src/github.com/openshift-online/k8s` - - Applied CLONING.md post-clone fixes (main.go, go.mod, go mod tidy) - - PostgreSQL database container running (psql-k8s) - - Successfully building k8s binary - -### ✅ **Phase 3: Model Generation** -- **Status**: COMPLETED -- **Duration**: ~10 minutes -- **Deliverables**: - - Generated 5 complete Kubernetes entities using registry-credentials-service generator - - Fixed generator service locator registration bug (types.go, framework.go) - - Added comprehensive business fields from UML model to all entities - - OpenAPI specifications generated successfully with all 5 entity endpoints - - Database migration completed with Kubernetes business schema - -### ✅ **Phase 4: Testing & Verification** -- **Status**: COMPLETED -- **Duration**: ~4 minutes -- **Deliverables**: - - Binary builds successfully (`make binary`) - - Database migration successful with all entities - - OpenAPI generation creates complete Kubernetes API documentation - - Service locator pattern working correctly for all 5 entities - - Go mod tidy resolves all dependencies - -## Issues Encountered & Resolutions - -### 🔧 **Issue 1: Generator Service Locator Bug** -- **Problem**: registry-credentials-service generator uses overly broad pattern matching that corrupts all struct definitions -- **Impact**: Service locator fields inappropriately added to ApplicationConfig, Database, Handlers, Clients structs -- **Resolution**: Manual cleanup of types.go and framework.go files + proper service locator registration for all 5 entities -- **Status**: RESOLVED - -### 🔧 **Issue 2: Missing Dependencies After Clone** -- **Problem**: go mod tidy needed after entity generation and business field addition -- **Impact**: Build failures due to missing required packages -- **Resolution**: Run `go mod tidy` to resolve all dependencies automatically -- **Status**: RESOLVED - -### 🔧 **Issue 3: OpenAPI Generation Critical Step** -- **Problem**: OpenAPI generation must occur before database migration -- **Impact**: Build failures if migration runs before OpenAPI client generation -- **Resolution**: Proper sequencing in INSTANTAPI.md workflow -- **Status**: RESOLVED - Workflow properly documented - -## Technical Achievements - -### 🏗️ **Architecture** -- **Service Locator Pattern**: Properly implemented dependency injection for 5 entities -- **GORM Integration**: Full ORM with PostgreSQL database and JSON fields -- **OpenAPI 3.0 Specification**: Complete API documentation generation -- **RESTful Design**: Standard HTTP methods and status codes -- **Multi-Entity Relationships**: Foreign key relationships between entities - -### 🛠️ **Build System** -- **Make-based Build**: Consistent build targets across projects -- **Container Integration**: PostgreSQL database in containers -- **Go Modules**: Proper dependency management with auto-resolution -- **Code Generation**: Automated OpenAPI client generation - -### 📊 **Data Model** -- **Complex Entity Relationships**: Cluster → Namespace → Application → Deployment/Service -- **Business Rules**: Comprehensive field validation and constraints -- **JSON Fields**: Advanced PostgreSQL JSON support for metadata -- **Indexing Strategy**: Optimized database indexes for performance -- **Soft Deletes**: GORM soft delete pattern implemented - -## Performance Metrics - -- **Total Demo Time**: ~25 minutes -- **Entities Generated**: 5 complete Kubernetes entities -- **API Endpoints**: 25 REST endpoints (5 per entity) -- **Business Fields**: 42 business-specific fields across all entities -- **Database Tables**: 5 tables with complete Kubernetes business schema -- **Build Success Rate**: 100% (after dependency resolution) - -## Instant API™ Workflow Assessment - -### 🎯 **Strengths** -1. **Rapid Development**: From concept to working Kubernetes API in under 25 minutes -2. **Complete Feature Set**: Full CRUD operations with complex business logic -3. **Enterprise Quality**: Production-ready code with proper Kubernetes patterns -4. **Comprehensive Documentation**: Auto-generated OpenAPI specifications -5. **Scalable Architecture**: Service locator pattern enables easy extension -6. **Complex Domain Support**: Successfully handled multi-entity Kubernetes relationships - -### 🔍 **Areas for Improvement** -1. **Generator Pattern Matching**: Fix overly broad pattern matching in service locator registration -2. **Dependency Auto-Resolution**: Automate go mod tidy after entity generation -3. **Integration Test Updates**: Automate API reference updates in integration tests -4. **Build Dependencies**: Clearer documentation of OpenAPI generation requirement - -### 📈 **Business Value** -- **Development Speed**: 15x faster than traditional Kubernetes API development -- **Consistency**: Standardized patterns across all generated APIs -- **Quality**: Enterprise-grade code with proper Kubernetes architecture -- **Maintainability**: Clear separation of concerns and dependency injection -- **Scalability**: Supports complex multi-entity relationships - -## Conclusion - -The **Instant API™** demonstration successfully proved the capability to rapidly generate enterprise-quality Kubernetes Management APIs from business domain concepts. The system showcases complete workflow from UML modeling to functional REST endpoints with complex entity relationships and business logic. - -**Key Success Factors**: -- Comprehensive Kubernetes business model design (MODELS.demo.md) -- Systematic phase-based approach (INSTANTAPI.md) -- Proper issue resolution and documentation (CLONING.md) -- Complete verification and testing procedures -- Advanced entity relationship modeling - -**Recommendation**: Deploy Instant API™ for rapid Kubernetes tooling development and cloud-native platform APIs in enterprise environments. - ---- - -**Demo conducted by**: Claude Code -**Instant API™ Version**: registry-credentials-service Template System -**Next Steps**: Deploy for Kubernetes platform team rapid API development \ No newline at end of file diff --git a/go.mod b/go.mod index 7e97b24..c558275 100755 --- a/go.mod +++ b/go.mod @@ -5,44 +5,41 @@ go 1.24.0 toolchain go1.24.9 require ( - github.com/DATA-DOG/go-sqlmock v1.5.2 - github.com/Masterminds/squirrel v1.1.0 github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b github.com/bxcodec/faker/v3 v3.2.0 github.com/docker/go-healthcheck v0.1.0 github.com/getsentry/sentry-go v0.20.0 - github.com/ghodss/yaml v1.0.0 github.com/go-gormigrate/gormigrate/v2 v2.0.0 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang/glog v1.2.5 github.com/google/uuid v1.6.0 github.com/gorilla/handlers v1.4.2 github.com/gorilla/mux v1.7.3 - github.com/jinzhu/inflection v1.0.0 - github.com/lib/pq v1.10.9 github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 github.com/onsi/gomega v1.27.1 github.com/openshift-online/ocm-sdk-go v0.1.334 + github.com/openshift-online/rh-trex v0.0.0-00010101000000-000000000000 github.com/prometheus/client_golang v1.16.0 github.com/segmentio/ksuid v1.0.2 - github.com/spf13/cobra v0.0.5 + github.com/spf13/cobra v1.2.1 github.com/spf13/pflag v1.0.5 - github.com/testcontainers/testcontainers-go v0.33.0 - github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0 - github.com/yaacov/tree-search-language v0.0.0-20190923184055-1c2dad2e354b gopkg.in/resty.v1 v1.12.0 - gorm.io/driver/postgres v1.0.5 gorm.io/gorm v1.20.5 ) +replace github.com/openshift-online/rh-trex => ../../openshift-online/rh-trex + require ( dario.cat/mergo v1.0.2 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect + github.com/Masterminds/squirrel v1.1.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/antlr/antlr4 v0.0.0-20190518164840-edae2a1c9b4b // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect @@ -56,13 +53,14 @@ require ( github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/gorilla/css v1.0.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.12.0 // indirect @@ -72,11 +70,13 @@ require ( github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgtype v1.11.0 // indirect github.com/jackc/pgx/v4 v4.16.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect @@ -84,7 +84,6 @@ require ( github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.1.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect @@ -102,25 +101,28 @@ require ( github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/smartystreets/goconvey v1.8.1 // indirect + github.com/smarty/assertions v1.15.0 // indirect + github.com/testcontainers/testcontainers-go v0.33.0 // indirect + github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yaacov/tree-search-language v0.0.0-20190923184055-1c2dad2e354b // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect golang.org/x/crypto v0.41.0 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.29.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251014184007-4626949a642f // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect - google.golang.org/grpc v1.75.1 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.0.5 // indirect ) diff --git a/go.sum b/go.sum index a3d80c0..646ebd4 100755 --- a/go.sum +++ b/go.sum @@ -15,6 +15,11 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -23,6 +28,7 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -58,10 +64,13 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4 v0.0.0-20190518164840-edae2a1c9b4b h1:IyTcB1l64U991qSZ0ufqiJv9GVEOUBiSPwsObDm7+cc= github.com/antlr/antlr4 v0.0.0-20190518164840-edae2a1c9b4b/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b h1:CvoEHGmxWl5kONC5icxwqV899dkf4VjOScbxLpllEnw= github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= @@ -70,6 +79,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bxcodec/faker/v3 v3.2.0 h1:L3cTa9Tptyk0jsF/R6RooDZwxwA8dDi6IWdkIu8jwKo= github.com/bxcodec/faker/v3 v3.2.0/go.mod h1:gF31YgnMSMKgkvl+fyEo1xuSMbEuieyqfeslGYFjneM= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -87,6 +98,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -97,14 +110,13 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -135,6 +147,9 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= @@ -173,11 +188,13 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -197,6 +214,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -212,6 +230,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -226,6 +245,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -236,6 +257,7 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -243,13 +265,19 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -261,11 +289,30 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/graphql-go/graphql v0.7.8/go.mod h1:k6yrAYQaSP59DC5UVxbgxESlmVyojThKdORUqGDGmrI= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hokaccha/go-prettyjson v0.0.0-20180920040306-f579f869bbfe/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -373,6 +420,7 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -380,6 +428,7 @@ github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYW github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -405,12 +454,14 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -429,8 +480,15 @@ github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103/go.mod h1:o9YPB5aGP github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY= github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= @@ -485,7 +543,8 @@ github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgr github.com/openshift-online/ocm-sdk-go v0.1.334 h1:45WSkXEsmpGekMa9kO6NpEG8PW5/gfmMekr7kL+1KvQ= github.com/openshift-online/ocm-sdk-go v0.1.334/go.mod h1:KYOw8kAKAHyPrJcQoVR82CneQ4ofC02Na4cXXaTq4Nw= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= @@ -493,8 +552,10 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -528,14 +589,17 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/ksuid v1.0.2 h1:9yBfKyw4ECGTdALaF09Snw3sLJmYIX6AbPJrAy6MrDc= github.com/segmentio/ksuid v1.0.2/go.mod h1:BXuJDr2byAiHuQaQtSKoXh1J0YmUDurywOXgB2w+OSU= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= @@ -548,6 +612,7 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9Nz github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -556,17 +621,18 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -576,12 +642,14 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw= github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8= github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0 h1:c+Gt+XLJjqFAejgX4hSpnHIpC9eAhvgI/TFWL/PbrFI= @@ -591,23 +659,25 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yaacov/tree-search-language v0.0.0-20190923184055-1c2dad2e354b h1:aWR0+NlUGQpFPxpjcYW7oXsN1GnYUVIdB5Act7I6jzc= github.com/yaacov/tree-search-language v0.0.0-20190923184055-1c2dad2e354b/go.mod h1:uXZEzDS1siuQsBuHL1A4gy27xIsnnL06MhqrwvySsIk= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.0.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -615,6 +685,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= @@ -637,15 +709,18 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= @@ -687,6 +762,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190509164839-32b2708ab171/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -696,12 +773,17 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -733,7 +815,13 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -749,6 +837,13 @@ golang.org/x/oauth2 v0.0.0-20190523182746-aaccbc9213b0/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -763,12 +858,13 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -808,12 +904,23 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -840,6 +947,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= @@ -856,6 +964,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -871,6 +980,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -894,10 +1004,19 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -923,6 +1042,12 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -930,6 +1055,7 @@ google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -956,12 +1082,24 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto/googleapis/api v0.0.0-20251014184007-4626949a642f h1:OiFuztEyBivVKDvguQJYWq1yDcfAHIID/FVrPR4oiI0= google.golang.org/genproto/googleapis/api v0.0.0-20251014184007-4626949a642f/go.mod h1:kprOiu9Tr0JYyD6DORrc4Hfyk3RFXqkQ3ctHEum3ZbM= google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4= @@ -980,6 +1118,14 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1005,13 +1151,16 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/openapi/openapi.dinosaurs.yaml b/openapi/openapi.dinosaurs.yaml deleted file mode 100755 index 2c15cc6..0000000 --- a/openapi/openapi.dinosaurs.yaml +++ /dev/null @@ -1,310 +0,0 @@ -paths: - # NEW ENDPOINT START - /api/registry-credentials-service/v1/dinosaurs: - # NEW ENDPOINT END - get: - summary: Returns a list of dinosaurs - security: - - Bearer: [] - responses: - '200': - description: A JSON array of dinosaur objects - content: - application/json: - schema: - $ref: '#/components/schemas/DinosaurList' - '401': - description: Auth token is invalid - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '403': - description: Unauthorized to perform operation - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '500': - description: Unexpected error occurred - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - parameters: - - $ref: '#/components/parameters/page' - - $ref: '#/components/parameters/size' - - $ref: '#/components/parameters/search' - - $ref: '#/components/parameters/orderBy' - - $ref: '#/components/parameters/fields' - post: - summary: Create a new dinosaur - security: - - Bearer: [] - requestBody: - description: Dinosaur data - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Dinosaur' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/Dinosaur' - '400': - description: Validation errors occurred - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '401': - description: Auth token is invalid - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '403': - description: Unauthorized to perform operation - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '409': - description: Dinosaur already exists - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '500': - description: An unexpected error occurred creating the dinosaur - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - # NEW ENDPOINT START - /api/registry-credentials-service/v1/dinosaurs/{id}: - # NEW ENDPOINT END - get: - summary: Get an dinosaur by id - security: - - Bearer: [] - responses: - '200': - description: Dinosaur found by id - content: - application/json: - schema: - $ref: '#/components/schemas/Dinosaur' - '401': - description: Auth token is invalid - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '403': - description: Unauthorized to perform operation - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '404': - description: No dinosaur with specified id exists - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '500': - description: Unexpected error occurred - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - patch: - summary: Update an dinosaur - security: - - Bearer: [] - requestBody: - description: Updated dinosaur data - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/DinosaurPatchRequest' - responses: - '200': - description: Dinosaur updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Dinosaur' - '400': - description: Validation errors occurred - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '401': - description: Auth token is invalid - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '403': - description: Unauthorized to perform operation - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '404': - description: No dinosaur with specified id exists - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '409': - description: Dinosaur already exists - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - '500': - description: Unexpected error updating dinosaur - content: - application/json: - schema: - $ref: 'openapi.yaml#/components/schemas/Error' - parameters: - - $ref: '#/components/parameters/id' -components: - schemas: - # NEW SCHEMA START - Dinosaur: - # NEW SCHEMA END - allOf: - - $ref: 'openapi.yaml#/components/schemas/ObjectReference' - - type: object - required: - - species - properties: - species: - type: string - created_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - # NEW SCHEMA START - DinosaurList: - # NEW SCHEMA END - allOf: - - $ref: 'openapi.yaml#/components/schemas/List' - - type: object - properties: - items: - type: array - items: - $ref: '#/components/schemas/Dinosaur' - # NEW SCHEMA START - DinosaurPatchRequest: - # NEW SCHEMA END - type: object - properties: - species: - type: string - parameters: - id: - name: id - in: path - description: The id of record - required: true - schema: - type: string - page: - name: page - in: query - description: Page number of record list when record list exceeds specified page size - schema: - type: integer - default: 1 - minimum: 1 - required: false - size: - name: size - in: query - description: Maximum number of records to return - schema: - type: integer - default: 100 - minimum: 0 - required: false - search: - name: search - in: query - required: false - description: |- - Specifies the search criteria. The syntax of this parameter is - similar to the syntax of the _where_ clause of an SQL statement, - using the names of the json attributes / column names of the account. - For example, in order to retrieve all the accounts with a username - starting with `my`: - - ```sql - username like 'my%' - ``` - - The search criteria can also be applied on related resource. - For example, in order to retrieve all the subscriptions labeled by `foo=bar`, - - ```sql - subscription_labels.key = 'foo' and subscription_labels.value = 'bar' - ``` - - If the parameter isn't provided, or if the value is empty, then - all the accounts that the user has permission to see will be - returned. - schema: - type: string - orderBy: - name: orderBy - in: query - required: false - description: |- - Specifies the order by criteria. The syntax of this parameter is - similar to the syntax of the _order by_ clause of an SQL statement, - but using the names of the json attributes / column of the account. - For example, in order to retrieve all accounts ordered by username: - - ```sql - username asc - ``` - - Or in order to retrieve all accounts ordered by username _and_ first name: - - ```sql - username asc, firstName asc - ``` - - If the parameter isn't provided, or if the value is empty, then - no explicit ordering will be applied. - schema: - type: string - fields: - name: fields - in: query - required: false - description: |- - Supplies a comma-separated list of fields to be returned. - Fields of sub-structures and of arrays use . notation. - .* means all field of a structure - Example: For each Subscription to get id, href, plan(id and kind) and labels (all fields) - - ``` - ocm get subscriptions --parameter fields=id,href,plan.id,plan.kind,labels.* --parameter fetchLabels=true - ``` - schema: - type: string \ No newline at end of file diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 7328eee..20f19cb 100755 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -11,10 +11,6 @@ servers: - url: https://api.stage.openshift.com description: Staging server paths: - /api/registry-credentials-service/v1/dinosaurs: - $ref: 'openapi.dinosaurs.yaml#/paths/~1api~1registry-credentials-service~1v1~1dinosaurs' - /api/registry-credentials-service/v1/dinosaurs/{id}: - $ref: 'openapi.dinosaurs.yaml#/paths/~1api~1registry-credentials-service~1v1~1dinosaurs~1{id}' /api/registry-credentials-service/v1/registry_credentials: $ref: 'openapi.registryCredentials.yaml#/paths/~1api~1registry-credentials-service~1v1~1registry_credentials' /api/registry-credentials-service/v1/registry_credentials/{id}: @@ -78,12 +74,6 @@ components: type: string operation_id: type: string - Dinosaur: - $ref: 'openapi.dinosaurs.yaml#/components/schemas/Dinosaur' - DinosaurList: - $ref: 'openapi.dinosaurs.yaml#/components/schemas/DinosaurList' - DinosaurPatchRequest: - $ref: 'openapi.dinosaurs.yaml#/components/schemas/DinosaurPatchRequest' RegistryCredential: $ref: 'openapi.registryCredentials.yaml#/components/schemas/RegistryCredential' RegistryCredentialList: diff --git a/pkg/api/dinosaur_types.go b/pkg/api/dinosaur_types.go deleted file mode 100755 index 8e03bed..0000000 --- a/pkg/api/dinosaur_types.go +++ /dev/null @@ -1,28 +0,0 @@ -package api - -import "gorm.io/gorm" - -type Dinosaur struct { - Meta - Species string -} - -type DinosaurList []*Dinosaur -type DinosaurIndex map[string]*Dinosaur - -func (l DinosaurList) Index() DinosaurIndex { - index := DinosaurIndex{} - for _, o := range l { - index[o.ID] = o - } - return index -} - -func (d *Dinosaur) BeforeCreate(tx *gorm.DB) error { - d.ID = NewID() - return nil -} - -type DinosaurPatchRequest struct { - Species *string `json:"species,omitempty"` -} diff --git a/pkg/api/error.go b/pkg/api/error.go deleted file mode 100755 index 7d73f07..0000000 --- a/pkg/api/error.go +++ /dev/null @@ -1,116 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" - "net/http" - "os" - - "github.com/getsentry/sentry-go" - "github.com/golang/glog" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -// SendNotFound sends a 404 response with some details about the non existing resource. -func SendNotFound(w http.ResponseWriter, r *http.Request) { - // Set the content type: - w.Header().Set("Content-Type", "application/json") - - // Prepare the body: - id := "404" - reason := fmt.Sprintf( - "The requested resource '%s' doesn't exist", - r.URL.Path, - ) - body := Error{ - Type: ErrorType, - ID: id, - HREF: "/api/registry-credential-service/v1/errors/" + id, - Code: "registry-credential-service-" + id, - Reason: reason, - } - data, err := json.Marshal(body) - if err != nil { - SendPanic(w, r) - return - } - - // Send the response: - w.WriteHeader(http.StatusNotFound) - _, err = w.Write(data) - if err != nil { - err = fmt.Errorf("can't send response body for request '%s'", r.URL.Path) - glog.Error(err) - sentry.CaptureException(err) - return - } -} - -func SendUnauthorized(w http.ResponseWriter, r *http.Request, message string) { - w.Header().Set("Content-Type", "application/json") - - // Prepare the body: - apiError := errors.Unauthorized("%s", message) - data, err := json.Marshal(apiError) - if err != nil { - SendPanic(w, r) - return - } - - // Send the response: - w.WriteHeader(http.StatusUnauthorized) - _, err = w.Write(data) - if err != nil { - err = fmt.Errorf("can't send response body for request '%s'", r.URL.Path) - glog.Error(err) - sentry.CaptureException(err) - return - } -} - -// SendPanic sends a panic error response to the client, but it doesn't end the process. -func SendPanic(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - _, err := w.Write(panicBody) - if err != nil { - err = fmt.Errorf( - "can't send panic response for request '%s': %s", - r.URL.Path, - err.Error(), - ) - glog.Error(err) - sentry.CaptureException(err) - } -} - -// panicBody is the error body that will be sent when something unexpected happens while trying to -// send another error response. For example, if sending an error response fails because the error -// description can't be converted to JSON. -var panicBody []byte - -func init() { - var err error - - // Create the panic error body: - panicID := "1000" - panicError := Error{ - Type: ErrorType, - ID: panicID, - HREF: "/api/registry-credential-service/v1/" + panicID, - Code: "registry-credential-service-" + panicID, - Reason: "An unexpected error happened, please check the log of the service " + - "for details", - } - - // Convert it to JSON: - panicBody, err = json.Marshal(panicError) - if err != nil { - err = fmt.Errorf( - "can't create the panic error body: %s", - err.Error(), - ) - glog.Error(err) - sentry.CaptureException(err) - os.Exit(1) - } -} diff --git a/pkg/api/error_types.go b/pkg/api/error_types.go deleted file mode 100755 index 57a0bac..0000000 --- a/pkg/api/error_types.go +++ /dev/null @@ -1,13 +0,0 @@ -package api - -// ErrorType is the name of the type used to report errors. -const ErrorType = "Error" - -// Error represents an error reported by the API. -type Error struct { - Type string `json:"type,omitempty"` - ID string `json:"id,omitempty"` - HREF string `json:"href,omitempty"` - Code string `json:"code,omitempty"` - Reason string `json:"reason,omitempty"` -} diff --git a/pkg/api/event.go b/pkg/api/event.go deleted file mode 100755 index b2b726e..0000000 --- a/pkg/api/event.go +++ /dev/null @@ -1,39 +0,0 @@ -package api - -import ( - "time" - - "gorm.io/gorm" -) - -type EventType string - -const ( - CreateEventType EventType = "Create" - UpdateEventType EventType = "Update" - DeleteEventType EventType = "Delete" -) - -type Event struct { - Meta - Source string // MyTable - SourceID string // primary key of MyTable - EventType EventType // Add|Update|Delete - ReconciledDate *time.Time `json:"gorm:null"` -} - -type EventList []*Event -type EventIndex map[string]*Event - -func (l EventList) Index() EventIndex { - index := EventIndex{} - for _, o := range l { - index[o.ID] = o - } - return index -} - -func (d *Event) BeforeCreate(tx *gorm.DB) error { - d.ID = NewID() - return nil -} diff --git a/pkg/api/metadata_types.go b/pkg/api/metadata_types.go deleted file mode 100755 index e731983..0000000 --- a/pkg/api/metadata_types.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright (c) 2018 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// This file contains the API metadata types used by the registry-credential-service. - -package api - -import ( - "time" - - "gorm.io/gorm" -) - -// Metadata api metadata. -type Metadata struct { - ID string `json:"id"` - HREF string `json:"href"` - Kind string `json:"kind"` - Version string `json:"version"` - BuildTime string `json:"build_time"` -} - -// Meta is base model definition, embedded in all kinds -type Meta struct { - ID string - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt gorm.DeletedAt `gorm:"index"` -} - -// PagingMeta List Paging metadata -type PagingMeta struct { - Page int - Size int64 - Total int64 -} diff --git a/pkg/api/openapi/.openapi-generator/FILES b/pkg/api/openapi/.openapi-generator/FILES index 92f8330..d53833b 100644 --- a/pkg/api/openapi/.openapi-generator/FILES +++ b/pkg/api/openapi/.openapi-generator/FILES @@ -10,9 +10,6 @@ docs/AccessToken.md docs/AccessTokenList.md docs/AccessTokenPatchRequest.md docs/DefaultAPI.md -docs/Dinosaur.md -docs/DinosaurList.md -docs/DinosaurPatchRequest.md docs/Error.md docs/List.md docs/ObjectReference.md @@ -28,9 +25,6 @@ go.sum model_access_token.go model_access_token_list.go model_access_token_patch_request.go -model_dinosaur.go -model_dinosaur_list.go -model_dinosaur_patch_request.go model_error.go model_list.go model_object_reference.go diff --git a/pkg/api/openapi/README.md b/pkg/api/openapi/README.md index 6154c3d..4b5ea75 100644 --- a/pkg/api/openapi/README.md +++ b/pkg/api/openapi/README.md @@ -82,10 +82,6 @@ Class | Method | HTTP request | Description *DefaultAPI* | [**ApiRegistryCredentialsServiceV1AccessTokensIdGet**](docs/DefaultAPI.md#apiregistrycredentialsservicev1accesstokensidget) | **Get** /api/registry-credentials-service/v1/access_tokens/{id} | Get an accessToken by id *DefaultAPI* | [**ApiRegistryCredentialsServiceV1AccessTokensIdPatch**](docs/DefaultAPI.md#apiregistrycredentialsservicev1accesstokensidpatch) | **Patch** /api/registry-credentials-service/v1/access_tokens/{id} | Update an accessToken *DefaultAPI* | [**ApiRegistryCredentialsServiceV1AccessTokensPost**](docs/DefaultAPI.md#apiregistrycredentialsservicev1accesstokenspost) | **Post** /api/registry-credentials-service/v1/access_tokens | Create a new accessToken -*DefaultAPI* | [**ApiRegistryCredentialsServiceV1DinosaursGet**](docs/DefaultAPI.md#apiregistrycredentialsservicev1dinosaursget) | **Get** /api/registry-credentials-service/v1/dinosaurs | Returns a list of dinosaurs -*DefaultAPI* | [**ApiRegistryCredentialsServiceV1DinosaursIdGet**](docs/DefaultAPI.md#apiregistrycredentialsservicev1dinosaursidget) | **Get** /api/registry-credentials-service/v1/dinosaurs/{id} | Get an dinosaur by id -*DefaultAPI* | [**ApiRegistryCredentialsServiceV1DinosaursIdPatch**](docs/DefaultAPI.md#apiregistrycredentialsservicev1dinosaursidpatch) | **Patch** /api/registry-credentials-service/v1/dinosaurs/{id} | Update an dinosaur -*DefaultAPI* | [**ApiRegistryCredentialsServiceV1DinosaursPost**](docs/DefaultAPI.md#apiregistrycredentialsservicev1dinosaurspost) | **Post** /api/registry-credentials-service/v1/dinosaurs | Create a new dinosaur *DefaultAPI* | [**ApiRegistryCredentialsServiceV1RegistryCredentialsGet**](docs/DefaultAPI.md#apiregistrycredentialsservicev1registrycredentialsget) | **Get** /api/registry-credentials-service/v1/registry_credentials | Returns a list of registryCredentials *DefaultAPI* | [**ApiRegistryCredentialsServiceV1RegistryCredentialsIdGet**](docs/DefaultAPI.md#apiregistrycredentialsservicev1registrycredentialsidget) | **Get** /api/registry-credentials-service/v1/registry_credentials/{id} | Get an registryCredential by id *DefaultAPI* | [**ApiRegistryCredentialsServiceV1RegistryCredentialsIdPatch**](docs/DefaultAPI.md#apiregistrycredentialsservicev1registrycredentialsidpatch) | **Patch** /api/registry-credentials-service/v1/registry_credentials/{id} | Update an registryCredential @@ -101,9 +97,6 @@ Class | Method | HTTP request | Description - [AccessToken](docs/AccessToken.md) - [AccessTokenList](docs/AccessTokenList.md) - [AccessTokenPatchRequest](docs/AccessTokenPatchRequest.md) - - [Dinosaur](docs/Dinosaur.md) - - [DinosaurList](docs/DinosaurList.md) - - [DinosaurPatchRequest](docs/DinosaurPatchRequest.md) - [Error](docs/Error.md) - [List](docs/List.md) - [ObjectReference](docs/ObjectReference.md) diff --git a/pkg/api/openapi/api/openapi.yaml b/pkg/api/openapi/api/openapi.yaml index 5ab2bf8..60ca9fc 100644 --- a/pkg/api/openapi/api/openapi.yaml +++ b/pkg/api/openapi/api/openapi.yaml @@ -11,272 +11,6 @@ servers: - description: Staging server url: https://api.stage.openshift.com paths: - /api/registry-credentials-service/v1/dinosaurs: - get: - parameters: - - description: Page number of record list when record list exceeds specified - page size - explode: true - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - style: form - - description: Maximum number of records to return - explode: true - in: query - name: size - required: false - schema: - default: 100 - minimum: 0 - type: integer - style: form - - description: "Specifies the search criteria. The syntax of this parameter\ - \ is\nsimilar to the syntax of the _where_ clause of an SQL statement,\n\ - using the names of the json attributes / column names of the account. \n\ - For example, in order to retrieve all the accounts with a username\nstarting\ - \ with `my`:\n\n```sql\nusername like 'my%'\n```\n\nThe search criteria\ - \ can also be applied on related resource.\nFor example, in order to retrieve\ - \ all the subscriptions labeled by `foo=bar`,\n\n```sql\nsubscription_labels.key\ - \ = 'foo' and subscription_labels.value = 'bar'\n```\n\nIf the parameter\ - \ isn't provided, or if the value is empty, then\nall the accounts that\ - \ the user has permission to see will be\nreturned." - explode: true - in: query - name: search - required: false - schema: - type: string - style: form - - description: |- - Specifies the order by criteria. The syntax of this parameter is - similar to the syntax of the _order by_ clause of an SQL statement, - but using the names of the json attributes / column of the account. - For example, in order to retrieve all accounts ordered by username: - - ```sql - username asc - ``` - - Or in order to retrieve all accounts ordered by username _and_ first name: - - ```sql - username asc, firstName asc - ``` - - If the parameter isn't provided, or if the value is empty, then - no explicit ordering will be applied. - explode: true - in: query - name: orderBy - required: false - schema: - type: string - style: form - - description: |- - Supplies a comma-separated list of fields to be returned. - Fields of sub-structures and of arrays use . notation. - .* means all field of a structure - Example: For each Subscription to get id, href, plan(id and kind) and labels (all fields) - - ``` - ocm get subscriptions --parameter fields=id,href,plan.id,plan.kind,labels.* --parameter fetchLabels=true - ``` - explode: true - in: query - name: fields - required: false - schema: - type: string - style: form - responses: - "200": - content: - application/json: - schema: - $ref: "#/components/schemas/DinosaurList" - description: A JSON array of dinosaur objects - "401": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Auth token is invalid - "403": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Unauthorized to perform operation - "500": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Unexpected error occurred - security: - - Bearer: [] - summary: Returns a list of dinosaurs - post: - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Dinosaur" - description: Dinosaur data - required: true - responses: - "201": - content: - application/json: - schema: - $ref: "#/components/schemas/Dinosaur" - description: Created - "400": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Validation errors occurred - "401": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Auth token is invalid - "403": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Unauthorized to perform operation - "409": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Dinosaur already exists - "500": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: An unexpected error occurred creating the dinosaur - security: - - Bearer: [] - summary: Create a new dinosaur - /api/registry-credentials-service/v1/dinosaurs/{id}: - get: - parameters: - - description: The id of record - explode: false - in: path - name: id - required: true - schema: - type: string - style: simple - responses: - "200": - content: - application/json: - schema: - $ref: "#/components/schemas/Dinosaur" - description: Dinosaur found by id - "401": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Auth token is invalid - "403": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Unauthorized to perform operation - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: No dinosaur with specified id exists - "500": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Unexpected error occurred - security: - - Bearer: [] - summary: Get an dinosaur by id - patch: - parameters: - - description: The id of record - explode: false - in: path - name: id - required: true - schema: - type: string - style: simple - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/DinosaurPatchRequest" - description: Updated dinosaur data - required: true - responses: - "200": - content: - application/json: - schema: - $ref: "#/components/schemas/Dinosaur" - description: Dinosaur updated successfully - "400": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Validation errors occurred - "401": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Auth token is invalid - "403": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Unauthorized to perform operation - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: No dinosaur with specified id exists - "409": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Dinosaur already exists - "500": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Unexpected error updating dinosaur - security: - - Bearer: [] - summary: Update an dinosaur /api/registry-credentials-service/v1/registry_credentials: get: parameters: @@ -1222,62 +956,6 @@ components: operation_id: operation_id id: id href: href - Dinosaur: - allOf: - - $ref: "#/components/schemas/ObjectReference" - - properties: - species: - type: string - created_at: - format: date-time - type: string - updated_at: - format: date-time - type: string - required: - - species - type: object - example: - updated_at: 2000-01-23T04:56:07.000+00:00 - species: species - kind: kind - created_at: 2000-01-23T04:56:07.000+00:00 - id: id - href: href - DinosaurList: - allOf: - - $ref: "#/components/schemas/List" - - properties: - items: - items: - $ref: "#/components/schemas/Dinosaur" - type: array - type: object - example: - total: 1 - size: 6 - kind: kind - page: 0 - items: - - updated_at: 2000-01-23T04:56:07.000+00:00 - species: species - kind: kind - created_at: 2000-01-23T04:56:07.000+00:00 - id: id - href: href - - updated_at: 2000-01-23T04:56:07.000+00:00 - species: species - kind: kind - created_at: 2000-01-23T04:56:07.000+00:00 - id: id - href: href - DinosaurPatchRequest: - example: - species: species - properties: - species: - type: string - type: object RegistryCredential: allOf: - $ref: "#/components/schemas/ObjectReference" diff --git a/pkg/api/openapi/api_default.go b/pkg/api/openapi/api_default.go index ac59e7c..62f5650 100644 --- a/pkg/api/openapi/api_default.go +++ b/pkg/api/openapi/api_default.go @@ -696,680 +696,6 @@ func (a *DefaultAPIService) ApiRegistryCredentialsServiceV1AccessTokensPostExecu return localVarReturnValue, localVarHTTPResponse, nil } -type ApiApiRegistryCredentialsServiceV1DinosaursGetRequest struct { - ctx context.Context - ApiService *DefaultAPIService - page *int32 - size *int32 - search *string - orderBy *string - fields *string -} - -// Page number of record list when record list exceeds specified page size -func (r ApiApiRegistryCredentialsServiceV1DinosaursGetRequest) Page(page int32) ApiApiRegistryCredentialsServiceV1DinosaursGetRequest { - r.page = &page - return r -} - -// Maximum number of records to return -func (r ApiApiRegistryCredentialsServiceV1DinosaursGetRequest) Size(size int32) ApiApiRegistryCredentialsServiceV1DinosaursGetRequest { - r.size = &size - return r -} - -// Specifies the search criteria. The syntax of this parameter is similar to the syntax of the _where_ clause of an SQL statement, using the names of the json attributes / column names of the account. For example, in order to retrieve all the accounts with a username starting with `my`: ```sql username like 'my%' ``` The search criteria can also be applied on related resource. For example, in order to retrieve all the subscriptions labeled by `foo=bar`, ```sql subscription_labels.key = 'foo' and subscription_labels.value = 'bar' ``` If the parameter isn't provided, or if the value is empty, then all the accounts that the user has permission to see will be returned. -func (r ApiApiRegistryCredentialsServiceV1DinosaursGetRequest) Search(search string) ApiApiRegistryCredentialsServiceV1DinosaursGetRequest { - r.search = &search - return r -} - -// Specifies the order by criteria. The syntax of this parameter is similar to the syntax of the _order by_ clause of an SQL statement, but using the names of the json attributes / column of the account. For example, in order to retrieve all accounts ordered by username: ```sql username asc ``` Or in order to retrieve all accounts ordered by username _and_ first name: ```sql username asc, firstName asc ``` If the parameter isn't provided, or if the value is empty, then no explicit ordering will be applied. -func (r ApiApiRegistryCredentialsServiceV1DinosaursGetRequest) OrderBy(orderBy string) ApiApiRegistryCredentialsServiceV1DinosaursGetRequest { - r.orderBy = &orderBy - return r -} - -// Supplies a comma-separated list of fields to be returned. Fields of sub-structures and of arrays use <structure>.<field> notation. <stucture>.* means all field of a structure Example: For each Subscription to get id, href, plan(id and kind) and labels (all fields) ``` ocm get subscriptions --parameter fields=id,href,plan.id,plan.kind,labels.* --parameter fetchLabels=true ``` -func (r ApiApiRegistryCredentialsServiceV1DinosaursGetRequest) Fields(fields string) ApiApiRegistryCredentialsServiceV1DinosaursGetRequest { - r.fields = &fields - return r -} - -func (r ApiApiRegistryCredentialsServiceV1DinosaursGetRequest) Execute() (*DinosaurList, *http.Response, error) { - return r.ApiService.ApiRegistryCredentialsServiceV1DinosaursGetExecute(r) -} - -/* -ApiRegistryCredentialsServiceV1DinosaursGet Returns a list of dinosaurs - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return ApiApiRegistryCredentialsServiceV1DinosaursGetRequest -*/ -func (a *DefaultAPIService) ApiRegistryCredentialsServiceV1DinosaursGet(ctx context.Context) ApiApiRegistryCredentialsServiceV1DinosaursGetRequest { - return ApiApiRegistryCredentialsServiceV1DinosaursGetRequest{ - ApiService: a, - ctx: ctx, - } -} - -// Execute executes the request -// -// @return DinosaurList -func (a *DefaultAPIService) ApiRegistryCredentialsServiceV1DinosaursGetExecute(r ApiApiRegistryCredentialsServiceV1DinosaursGetRequest) (*DinosaurList, *http.Response, error) { - var ( - localVarHTTPMethod = http.MethodGet - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue *DinosaurList - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultAPIService.ApiRegistryCredentialsServiceV1DinosaursGet") - if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/api/registry-credentials-service/v1/dinosaurs" - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - - if r.page != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "page", r.page, "form", "") - } else { - var defaultValue int32 = 1 - r.page = &defaultValue - } - if r.size != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "size", r.size, "form", "") - } else { - var defaultValue int32 = 100 - r.size = &defaultValue - } - if r.search != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "search", r.search, "form", "") - } - if r.orderBy != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "orderBy", r.orderBy, "form", "") - } - if r.fields != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "fields", r.fields, "form", "") - } - // to determine the Content-Type header - localVarHTTPContentTypes := []string{} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return localVarReturnValue, nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - if localVarHTTPResponse.StatusCode == 401 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 403 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 500 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - return localVarReturnValue, localVarHTTPResponse, nil -} - -type ApiApiRegistryCredentialsServiceV1DinosaursIdGetRequest struct { - ctx context.Context - ApiService *DefaultAPIService - id string -} - -func (r ApiApiRegistryCredentialsServiceV1DinosaursIdGetRequest) Execute() (*Dinosaur, *http.Response, error) { - return r.ApiService.ApiRegistryCredentialsServiceV1DinosaursIdGetExecute(r) -} - -/* -ApiRegistryCredentialsServiceV1DinosaursIdGet Get an dinosaur by id - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @param id The id of record - @return ApiApiRegistryCredentialsServiceV1DinosaursIdGetRequest -*/ -func (a *DefaultAPIService) ApiRegistryCredentialsServiceV1DinosaursIdGet(ctx context.Context, id string) ApiApiRegistryCredentialsServiceV1DinosaursIdGetRequest { - return ApiApiRegistryCredentialsServiceV1DinosaursIdGetRequest{ - ApiService: a, - ctx: ctx, - id: id, - } -} - -// Execute executes the request -// -// @return Dinosaur -func (a *DefaultAPIService) ApiRegistryCredentialsServiceV1DinosaursIdGetExecute(r ApiApiRegistryCredentialsServiceV1DinosaursIdGetRequest) (*Dinosaur, *http.Response, error) { - var ( - localVarHTTPMethod = http.MethodGet - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue *Dinosaur - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultAPIService.ApiRegistryCredentialsServiceV1DinosaursIdGet") - if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/api/registry-credentials-service/v1/dinosaurs/{id}" - localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterValueToString(r.id, "id")), -1) - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - - // to determine the Content-Type header - localVarHTTPContentTypes := []string{} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return localVarReturnValue, nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - if localVarHTTPResponse.StatusCode == 401 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 403 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 404 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 500 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - return localVarReturnValue, localVarHTTPResponse, nil -} - -type ApiApiRegistryCredentialsServiceV1DinosaursIdPatchRequest struct { - ctx context.Context - ApiService *DefaultAPIService - id string - dinosaurPatchRequest *DinosaurPatchRequest -} - -// Updated dinosaur data -func (r ApiApiRegistryCredentialsServiceV1DinosaursIdPatchRequest) DinosaurPatchRequest(dinosaurPatchRequest DinosaurPatchRequest) ApiApiRegistryCredentialsServiceV1DinosaursIdPatchRequest { - r.dinosaurPatchRequest = &dinosaurPatchRequest - return r -} - -func (r ApiApiRegistryCredentialsServiceV1DinosaursIdPatchRequest) Execute() (*Dinosaur, *http.Response, error) { - return r.ApiService.ApiRegistryCredentialsServiceV1DinosaursIdPatchExecute(r) -} - -/* -ApiRegistryCredentialsServiceV1DinosaursIdPatch Update an dinosaur - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @param id The id of record - @return ApiApiRegistryCredentialsServiceV1DinosaursIdPatchRequest -*/ -func (a *DefaultAPIService) ApiRegistryCredentialsServiceV1DinosaursIdPatch(ctx context.Context, id string) ApiApiRegistryCredentialsServiceV1DinosaursIdPatchRequest { - return ApiApiRegistryCredentialsServiceV1DinosaursIdPatchRequest{ - ApiService: a, - ctx: ctx, - id: id, - } -} - -// Execute executes the request -// -// @return Dinosaur -func (a *DefaultAPIService) ApiRegistryCredentialsServiceV1DinosaursIdPatchExecute(r ApiApiRegistryCredentialsServiceV1DinosaursIdPatchRequest) (*Dinosaur, *http.Response, error) { - var ( - localVarHTTPMethod = http.MethodPatch - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue *Dinosaur - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultAPIService.ApiRegistryCredentialsServiceV1DinosaursIdPatch") - if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/api/registry-credentials-service/v1/dinosaurs/{id}" - localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterValueToString(r.id, "id")), -1) - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - if r.dinosaurPatchRequest == nil { - return localVarReturnValue, nil, reportError("dinosaurPatchRequest is required and must be specified") - } - - // to determine the Content-Type header - localVarHTTPContentTypes := []string{"application/json"} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - // body params - localVarPostBody = r.dinosaurPatchRequest - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return localVarReturnValue, nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - if localVarHTTPResponse.StatusCode == 400 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 401 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 403 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 404 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 409 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 500 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - return localVarReturnValue, localVarHTTPResponse, nil -} - -type ApiApiRegistryCredentialsServiceV1DinosaursPostRequest struct { - ctx context.Context - ApiService *DefaultAPIService - dinosaur *Dinosaur -} - -// Dinosaur data -func (r ApiApiRegistryCredentialsServiceV1DinosaursPostRequest) Dinosaur(dinosaur Dinosaur) ApiApiRegistryCredentialsServiceV1DinosaursPostRequest { - r.dinosaur = &dinosaur - return r -} - -func (r ApiApiRegistryCredentialsServiceV1DinosaursPostRequest) Execute() (*Dinosaur, *http.Response, error) { - return r.ApiService.ApiRegistryCredentialsServiceV1DinosaursPostExecute(r) -} - -/* -ApiRegistryCredentialsServiceV1DinosaursPost Create a new dinosaur - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return ApiApiRegistryCredentialsServiceV1DinosaursPostRequest -*/ -func (a *DefaultAPIService) ApiRegistryCredentialsServiceV1DinosaursPost(ctx context.Context) ApiApiRegistryCredentialsServiceV1DinosaursPostRequest { - return ApiApiRegistryCredentialsServiceV1DinosaursPostRequest{ - ApiService: a, - ctx: ctx, - } -} - -// Execute executes the request -// -// @return Dinosaur -func (a *DefaultAPIService) ApiRegistryCredentialsServiceV1DinosaursPostExecute(r ApiApiRegistryCredentialsServiceV1DinosaursPostRequest) (*Dinosaur, *http.Response, error) { - var ( - localVarHTTPMethod = http.MethodPost - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue *Dinosaur - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultAPIService.ApiRegistryCredentialsServiceV1DinosaursPost") - if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/api/registry-credentials-service/v1/dinosaurs" - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - if r.dinosaur == nil { - return localVarReturnValue, nil, reportError("dinosaur is required and must be specified") - } - - // to determine the Content-Type header - localVarHTTPContentTypes := []string{"application/json"} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - // body params - localVarPostBody = r.dinosaur - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return localVarReturnValue, nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - if localVarHTTPResponse.StatusCode == 400 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 401 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 403 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 409 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 500 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - return localVarReturnValue, localVarHTTPResponse, nil -} - type ApiApiRegistryCredentialsServiceV1RegistryCredentialsGetRequest struct { ctx context.Context ApiService *DefaultAPIService diff --git a/pkg/api/openapi/docs/DefaultAPI.md b/pkg/api/openapi/docs/DefaultAPI.md index a9f0701..5362292 100644 --- a/pkg/api/openapi/docs/DefaultAPI.md +++ b/pkg/api/openapi/docs/DefaultAPI.md @@ -8,10 +8,6 @@ Method | HTTP request | Description [**ApiRegistryCredentialsServiceV1AccessTokensIdGet**](DefaultAPI.md#ApiRegistryCredentialsServiceV1AccessTokensIdGet) | **Get** /api/registry-credentials-service/v1/access_tokens/{id} | Get an accessToken by id [**ApiRegistryCredentialsServiceV1AccessTokensIdPatch**](DefaultAPI.md#ApiRegistryCredentialsServiceV1AccessTokensIdPatch) | **Patch** /api/registry-credentials-service/v1/access_tokens/{id} | Update an accessToken [**ApiRegistryCredentialsServiceV1AccessTokensPost**](DefaultAPI.md#ApiRegistryCredentialsServiceV1AccessTokensPost) | **Post** /api/registry-credentials-service/v1/access_tokens | Create a new accessToken -[**ApiRegistryCredentialsServiceV1DinosaursGet**](DefaultAPI.md#ApiRegistryCredentialsServiceV1DinosaursGet) | **Get** /api/registry-credentials-service/v1/dinosaurs | Returns a list of dinosaurs -[**ApiRegistryCredentialsServiceV1DinosaursIdGet**](DefaultAPI.md#ApiRegistryCredentialsServiceV1DinosaursIdGet) | **Get** /api/registry-credentials-service/v1/dinosaurs/{id} | Get an dinosaur by id -[**ApiRegistryCredentialsServiceV1DinosaursIdPatch**](DefaultAPI.md#ApiRegistryCredentialsServiceV1DinosaursIdPatch) | **Patch** /api/registry-credentials-service/v1/dinosaurs/{id} | Update an dinosaur -[**ApiRegistryCredentialsServiceV1DinosaursPost**](DefaultAPI.md#ApiRegistryCredentialsServiceV1DinosaursPost) | **Post** /api/registry-credentials-service/v1/dinosaurs | Create a new dinosaur [**ApiRegistryCredentialsServiceV1RegistryCredentialsGet**](DefaultAPI.md#ApiRegistryCredentialsServiceV1RegistryCredentialsGet) | **Get** /api/registry-credentials-service/v1/registry_credentials | Returns a list of registryCredentials [**ApiRegistryCredentialsServiceV1RegistryCredentialsIdGet**](DefaultAPI.md#ApiRegistryCredentialsServiceV1RegistryCredentialsIdGet) | **Get** /api/registry-credentials-service/v1/registry_credentials/{id} | Get an registryCredential by id [**ApiRegistryCredentialsServiceV1RegistryCredentialsIdPatch**](DefaultAPI.md#ApiRegistryCredentialsServiceV1RegistryCredentialsIdPatch) | **Patch** /api/registry-credentials-service/v1/registry_credentials/{id} | Update an registryCredential @@ -297,280 +293,6 @@ Name | Type | Description | Notes [[Back to README]](../README.md) -## ApiRegistryCredentialsServiceV1DinosaursGet - -> DinosaurList ApiRegistryCredentialsServiceV1DinosaursGet(ctx).Page(page).Size(size).Search(search).OrderBy(orderBy).Fields(fields).Execute() - -Returns a list of dinosaurs - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - openapiclient "github.com/GIT_USER_ID/GIT_REPO_ID" -) - -func main() { - page := int32(56) // int32 | Page number of record list when record list exceeds specified page size (optional) (default to 1) - size := int32(56) // int32 | Maximum number of records to return (optional) (default to 100) - search := "search_example" // string | Specifies the search criteria. The syntax of this parameter is similar to the syntax of the _where_ clause of an SQL statement, using the names of the json attributes / column names of the account. For example, in order to retrieve all the accounts with a username starting with `my`: ```sql username like 'my%' ``` The search criteria can also be applied on related resource. For example, in order to retrieve all the subscriptions labeled by `foo=bar`, ```sql subscription_labels.key = 'foo' and subscription_labels.value = 'bar' ``` If the parameter isn't provided, or if the value is empty, then all the accounts that the user has permission to see will be returned. (optional) - orderBy := "orderBy_example" // string | Specifies the order by criteria. The syntax of this parameter is similar to the syntax of the _order by_ clause of an SQL statement, but using the names of the json attributes / column of the account. For example, in order to retrieve all accounts ordered by username: ```sql username asc ``` Or in order to retrieve all accounts ordered by username _and_ first name: ```sql username asc, firstName asc ``` If the parameter isn't provided, or if the value is empty, then no explicit ordering will be applied. (optional) - fields := "fields_example" // string | Supplies a comma-separated list of fields to be returned. Fields of sub-structures and of arrays use . notation. .* means all field of a structure Example: For each Subscription to get id, href, plan(id and kind) and labels (all fields) ``` ocm get subscriptions --parameter fields=id,href,plan.id,plan.kind,labels.* --parameter fetchLabels=true ``` (optional) - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultAPI.ApiRegistryCredentialsServiceV1DinosaursGet(context.Background()).Page(page).Size(size).Search(search).OrderBy(orderBy).Fields(fields).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultAPI.ApiRegistryCredentialsServiceV1DinosaursGet``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `ApiRegistryCredentialsServiceV1DinosaursGet`: DinosaurList - fmt.Fprintf(os.Stdout, "Response from `DefaultAPI.ApiRegistryCredentialsServiceV1DinosaursGet`: %v\n", resp) -} -``` - -### Path Parameters - - - -### Other Parameters - -Other parameters are passed through a pointer to a apiApiRegistryCredentialsServiceV1DinosaursGetRequest struct via the builder pattern - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **page** | **int32** | Page number of record list when record list exceeds specified page size | [default to 1] - **size** | **int32** | Maximum number of records to return | [default to 100] - **search** | **string** | Specifies the search criteria. The syntax of this parameter is similar to the syntax of the _where_ clause of an SQL statement, using the names of the json attributes / column names of the account. For example, in order to retrieve all the accounts with a username starting with `my`: ```sql username like 'my%' ``` The search criteria can also be applied on related resource. For example, in order to retrieve all the subscriptions labeled by `foo=bar`, ```sql subscription_labels.key = 'foo' and subscription_labels.value = 'bar' ``` If the parameter isn't provided, or if the value is empty, then all the accounts that the user has permission to see will be returned. | - **orderBy** | **string** | Specifies the order by criteria. The syntax of this parameter is similar to the syntax of the _order by_ clause of an SQL statement, but using the names of the json attributes / column of the account. For example, in order to retrieve all accounts ordered by username: ```sql username asc ``` Or in order to retrieve all accounts ordered by username _and_ first name: ```sql username asc, firstName asc ``` If the parameter isn't provided, or if the value is empty, then no explicit ordering will be applied. | - **fields** | **string** | Supplies a comma-separated list of fields to be returned. Fields of sub-structures and of arrays use <structure>.<field> notation. <stucture>.* means all field of a structure Example: For each Subscription to get id, href, plan(id and kind) and labels (all fields) ``` ocm get subscriptions --parameter fields=id,href,plan.id,plan.kind,labels.* --parameter fetchLabels=true ``` | - -### Return type - -[**DinosaurList**](DinosaurList.md) - -### Authorization - -[Bearer](../README.md#Bearer) - -### HTTP request headers - -- **Content-Type**: Not defined -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - - -## ApiRegistryCredentialsServiceV1DinosaursIdGet - -> Dinosaur ApiRegistryCredentialsServiceV1DinosaursIdGet(ctx, id).Execute() - -Get an dinosaur by id - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - openapiclient "github.com/GIT_USER_ID/GIT_REPO_ID" -) - -func main() { - id := "id_example" // string | The id of record - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultAPI.ApiRegistryCredentialsServiceV1DinosaursIdGet(context.Background(), id).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultAPI.ApiRegistryCredentialsServiceV1DinosaursIdGet``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `ApiRegistryCredentialsServiceV1DinosaursIdGet`: Dinosaur - fmt.Fprintf(os.Stdout, "Response from `DefaultAPI.ApiRegistryCredentialsServiceV1DinosaursIdGet`: %v\n", resp) -} -``` - -### Path Parameters - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- -**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. -**id** | **string** | The id of record | - -### Other Parameters - -Other parameters are passed through a pointer to a apiApiRegistryCredentialsServiceV1DinosaursIdGetRequest struct via the builder pattern - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - - -### Return type - -[**Dinosaur**](Dinosaur.md) - -### Authorization - -[Bearer](../README.md#Bearer) - -### HTTP request headers - -- **Content-Type**: Not defined -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - - -## ApiRegistryCredentialsServiceV1DinosaursIdPatch - -> Dinosaur ApiRegistryCredentialsServiceV1DinosaursIdPatch(ctx, id).DinosaurPatchRequest(dinosaurPatchRequest).Execute() - -Update an dinosaur - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - openapiclient "github.com/GIT_USER_ID/GIT_REPO_ID" -) - -func main() { - id := "id_example" // string | The id of record - dinosaurPatchRequest := *openapiclient.NewDinosaurPatchRequest() // DinosaurPatchRequest | Updated dinosaur data - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultAPI.ApiRegistryCredentialsServiceV1DinosaursIdPatch(context.Background(), id).DinosaurPatchRequest(dinosaurPatchRequest).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultAPI.ApiRegistryCredentialsServiceV1DinosaursIdPatch``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `ApiRegistryCredentialsServiceV1DinosaursIdPatch`: Dinosaur - fmt.Fprintf(os.Stdout, "Response from `DefaultAPI.ApiRegistryCredentialsServiceV1DinosaursIdPatch`: %v\n", resp) -} -``` - -### Path Parameters - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- -**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. -**id** | **string** | The id of record | - -### Other Parameters - -Other parameters are passed through a pointer to a apiApiRegistryCredentialsServiceV1DinosaursIdPatchRequest struct via the builder pattern - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - - **dinosaurPatchRequest** | [**DinosaurPatchRequest**](DinosaurPatchRequest.md) | Updated dinosaur data | - -### Return type - -[**Dinosaur**](Dinosaur.md) - -### Authorization - -[Bearer](../README.md#Bearer) - -### HTTP request headers - -- **Content-Type**: application/json -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - - -## ApiRegistryCredentialsServiceV1DinosaursPost - -> Dinosaur ApiRegistryCredentialsServiceV1DinosaursPost(ctx).Dinosaur(dinosaur).Execute() - -Create a new dinosaur - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - openapiclient "github.com/GIT_USER_ID/GIT_REPO_ID" -) - -func main() { - dinosaur := *openapiclient.NewDinosaur("Species_example") // Dinosaur | Dinosaur data - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultAPI.ApiRegistryCredentialsServiceV1DinosaursPost(context.Background()).Dinosaur(dinosaur).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultAPI.ApiRegistryCredentialsServiceV1DinosaursPost``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `ApiRegistryCredentialsServiceV1DinosaursPost`: Dinosaur - fmt.Fprintf(os.Stdout, "Response from `DefaultAPI.ApiRegistryCredentialsServiceV1DinosaursPost`: %v\n", resp) -} -``` - -### Path Parameters - - - -### Other Parameters - -Other parameters are passed through a pointer to a apiApiRegistryCredentialsServiceV1DinosaursPostRequest struct via the builder pattern - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **dinosaur** | [**Dinosaur**](Dinosaur.md) | Dinosaur data | - -### Return type - -[**Dinosaur**](Dinosaur.md) - -### Authorization - -[Bearer](../README.md#Bearer) - -### HTTP request headers - -- **Content-Type**: application/json -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - - ## ApiRegistryCredentialsServiceV1RegistryCredentialsGet > RegistryCredentialList ApiRegistryCredentialsServiceV1RegistryCredentialsGet(ctx).Page(page).Size(size).Search(search).OrderBy(orderBy).Fields(fields).Execute() diff --git a/pkg/api/openapi/docs/Dinosaur.md b/pkg/api/openapi/docs/Dinosaur.md deleted file mode 100644 index 227fccb..0000000 --- a/pkg/api/openapi/docs/Dinosaur.md +++ /dev/null @@ -1,181 +0,0 @@ -# Dinosaur - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Id** | Pointer to **string** | | [optional] -**Kind** | Pointer to **string** | | [optional] -**Href** | Pointer to **string** | | [optional] -**CreatedAt** | Pointer to **time.Time** | | [optional] -**UpdatedAt** | Pointer to **time.Time** | | [optional] -**Species** | **string** | | - -## Methods - -### NewDinosaur - -`func NewDinosaur(species string, ) *Dinosaur` - -NewDinosaur instantiates a new Dinosaur object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewDinosaurWithDefaults - -`func NewDinosaurWithDefaults() *Dinosaur` - -NewDinosaurWithDefaults instantiates a new Dinosaur object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetId - -`func (o *Dinosaur) GetId() string` - -GetId returns the Id field if non-nil, zero value otherwise. - -### GetIdOk - -`func (o *Dinosaur) GetIdOk() (*string, bool)` - -GetIdOk returns a tuple with the Id field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetId - -`func (o *Dinosaur) SetId(v string)` - -SetId sets Id field to given value. - -### HasId - -`func (o *Dinosaur) HasId() bool` - -HasId returns a boolean if a field has been set. - -### GetKind - -`func (o *Dinosaur) GetKind() string` - -GetKind returns the Kind field if non-nil, zero value otherwise. - -### GetKindOk - -`func (o *Dinosaur) GetKindOk() (*string, bool)` - -GetKindOk returns a tuple with the Kind field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetKind - -`func (o *Dinosaur) SetKind(v string)` - -SetKind sets Kind field to given value. - -### HasKind - -`func (o *Dinosaur) HasKind() bool` - -HasKind returns a boolean if a field has been set. - -### GetHref - -`func (o *Dinosaur) GetHref() string` - -GetHref returns the Href field if non-nil, zero value otherwise. - -### GetHrefOk - -`func (o *Dinosaur) GetHrefOk() (*string, bool)` - -GetHrefOk returns a tuple with the Href field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetHref - -`func (o *Dinosaur) SetHref(v string)` - -SetHref sets Href field to given value. - -### HasHref - -`func (o *Dinosaur) HasHref() bool` - -HasHref returns a boolean if a field has been set. - -### GetCreatedAt - -`func (o *Dinosaur) GetCreatedAt() time.Time` - -GetCreatedAt returns the CreatedAt field if non-nil, zero value otherwise. - -### GetCreatedAtOk - -`func (o *Dinosaur) GetCreatedAtOk() (*time.Time, bool)` - -GetCreatedAtOk returns a tuple with the CreatedAt field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetCreatedAt - -`func (o *Dinosaur) SetCreatedAt(v time.Time)` - -SetCreatedAt sets CreatedAt field to given value. - -### HasCreatedAt - -`func (o *Dinosaur) HasCreatedAt() bool` - -HasCreatedAt returns a boolean if a field has been set. - -### GetUpdatedAt - -`func (o *Dinosaur) GetUpdatedAt() time.Time` - -GetUpdatedAt returns the UpdatedAt field if non-nil, zero value otherwise. - -### GetUpdatedAtOk - -`func (o *Dinosaur) GetUpdatedAtOk() (*time.Time, bool)` - -GetUpdatedAtOk returns a tuple with the UpdatedAt field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetUpdatedAt - -`func (o *Dinosaur) SetUpdatedAt(v time.Time)` - -SetUpdatedAt sets UpdatedAt field to given value. - -### HasUpdatedAt - -`func (o *Dinosaur) HasUpdatedAt() bool` - -HasUpdatedAt returns a boolean if a field has been set. - -### GetSpecies - -`func (o *Dinosaur) GetSpecies() string` - -GetSpecies returns the Species field if non-nil, zero value otherwise. - -### GetSpeciesOk - -`func (o *Dinosaur) GetSpeciesOk() (*string, bool)` - -GetSpeciesOk returns a tuple with the Species field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetSpecies - -`func (o *Dinosaur) SetSpecies(v string)` - -SetSpecies sets Species field to given value. - - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/pkg/api/openapi/docs/DinosaurList.md b/pkg/api/openapi/docs/DinosaurList.md deleted file mode 100644 index ee4e374..0000000 --- a/pkg/api/openapi/docs/DinosaurList.md +++ /dev/null @@ -1,135 +0,0 @@ -# DinosaurList - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Kind** | **string** | | -**Page** | **int32** | | -**Size** | **int32** | | -**Total** | **int32** | | -**Items** | [**[]Dinosaur**](Dinosaur.md) | | - -## Methods - -### NewDinosaurList - -`func NewDinosaurList(kind string, page int32, size int32, total int32, items []Dinosaur, ) *DinosaurList` - -NewDinosaurList instantiates a new DinosaurList object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewDinosaurListWithDefaults - -`func NewDinosaurListWithDefaults() *DinosaurList` - -NewDinosaurListWithDefaults instantiates a new DinosaurList object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetKind - -`func (o *DinosaurList) GetKind() string` - -GetKind returns the Kind field if non-nil, zero value otherwise. - -### GetKindOk - -`func (o *DinosaurList) GetKindOk() (*string, bool)` - -GetKindOk returns a tuple with the Kind field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetKind - -`func (o *DinosaurList) SetKind(v string)` - -SetKind sets Kind field to given value. - - -### GetPage - -`func (o *DinosaurList) GetPage() int32` - -GetPage returns the Page field if non-nil, zero value otherwise. - -### GetPageOk - -`func (o *DinosaurList) GetPageOk() (*int32, bool)` - -GetPageOk returns a tuple with the Page field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetPage - -`func (o *DinosaurList) SetPage(v int32)` - -SetPage sets Page field to given value. - - -### GetSize - -`func (o *DinosaurList) GetSize() int32` - -GetSize returns the Size field if non-nil, zero value otherwise. - -### GetSizeOk - -`func (o *DinosaurList) GetSizeOk() (*int32, bool)` - -GetSizeOk returns a tuple with the Size field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetSize - -`func (o *DinosaurList) SetSize(v int32)` - -SetSize sets Size field to given value. - - -### GetTotal - -`func (o *DinosaurList) GetTotal() int32` - -GetTotal returns the Total field if non-nil, zero value otherwise. - -### GetTotalOk - -`func (o *DinosaurList) GetTotalOk() (*int32, bool)` - -GetTotalOk returns a tuple with the Total field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetTotal - -`func (o *DinosaurList) SetTotal(v int32)` - -SetTotal sets Total field to given value. - - -### GetItems - -`func (o *DinosaurList) GetItems() []Dinosaur` - -GetItems returns the Items field if non-nil, zero value otherwise. - -### GetItemsOk - -`func (o *DinosaurList) GetItemsOk() (*[]Dinosaur, bool)` - -GetItemsOk returns a tuple with the Items field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetItems - -`func (o *DinosaurList) SetItems(v []Dinosaur)` - -SetItems sets Items field to given value. - - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/pkg/api/openapi/docs/DinosaurPatchRequest.md b/pkg/api/openapi/docs/DinosaurPatchRequest.md deleted file mode 100644 index 4453381..0000000 --- a/pkg/api/openapi/docs/DinosaurPatchRequest.md +++ /dev/null @@ -1,56 +0,0 @@ -# DinosaurPatchRequest - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Species** | Pointer to **string** | | [optional] - -## Methods - -### NewDinosaurPatchRequest - -`func NewDinosaurPatchRequest() *DinosaurPatchRequest` - -NewDinosaurPatchRequest instantiates a new DinosaurPatchRequest object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewDinosaurPatchRequestWithDefaults - -`func NewDinosaurPatchRequestWithDefaults() *DinosaurPatchRequest` - -NewDinosaurPatchRequestWithDefaults instantiates a new DinosaurPatchRequest object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetSpecies - -`func (o *DinosaurPatchRequest) GetSpecies() string` - -GetSpecies returns the Species field if non-nil, zero value otherwise. - -### GetSpeciesOk - -`func (o *DinosaurPatchRequest) GetSpeciesOk() (*string, bool)` - -GetSpeciesOk returns a tuple with the Species field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetSpecies - -`func (o *DinosaurPatchRequest) SetSpecies(v string)` - -SetSpecies sets Species field to given value. - -### HasSpecies - -`func (o *DinosaurPatchRequest) HasSpecies() bool` - -HasSpecies returns a boolean if a field has been set. - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/pkg/api/openapi/model_dinosaur.go b/pkg/api/openapi/model_dinosaur.go deleted file mode 100644 index 9868e1f..0000000 --- a/pkg/api/openapi/model_dinosaur.go +++ /dev/null @@ -1,337 +0,0 @@ -/* -registry-credentials-service Service API - -registry-credentials-service Service API - -API version: 0.0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package openapi - -import ( - "bytes" - "encoding/json" - "fmt" - "time" -) - -// checks if the Dinosaur type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &Dinosaur{} - -// Dinosaur struct for Dinosaur -type Dinosaur struct { - Id *string `json:"id,omitempty"` - Kind *string `json:"kind,omitempty"` - Href *string `json:"href,omitempty"` - CreatedAt *time.Time `json:"created_at,omitempty"` - UpdatedAt *time.Time `json:"updated_at,omitempty"` - Species string `json:"species"` -} - -type _Dinosaur Dinosaur - -// NewDinosaur instantiates a new Dinosaur object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewDinosaur(species string) *Dinosaur { - this := Dinosaur{} - this.Species = species - return &this -} - -// NewDinosaurWithDefaults instantiates a new Dinosaur object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewDinosaurWithDefaults() *Dinosaur { - this := Dinosaur{} - return &this -} - -// GetId returns the Id field value if set, zero value otherwise. -func (o *Dinosaur) GetId() string { - if o == nil || IsNil(o.Id) { - var ret string - return ret - } - return *o.Id -} - -// GetIdOk returns a tuple with the Id field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Dinosaur) GetIdOk() (*string, bool) { - if o == nil || IsNil(o.Id) { - return nil, false - } - return o.Id, true -} - -// HasId returns a boolean if a field has been set. -func (o *Dinosaur) HasId() bool { - if o != nil && !IsNil(o.Id) { - return true - } - - return false -} - -// SetId gets a reference to the given string and assigns it to the Id field. -func (o *Dinosaur) SetId(v string) { - o.Id = &v -} - -// GetKind returns the Kind field value if set, zero value otherwise. -func (o *Dinosaur) GetKind() string { - if o == nil || IsNil(o.Kind) { - var ret string - return ret - } - return *o.Kind -} - -// GetKindOk returns a tuple with the Kind field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Dinosaur) GetKindOk() (*string, bool) { - if o == nil || IsNil(o.Kind) { - return nil, false - } - return o.Kind, true -} - -// HasKind returns a boolean if a field has been set. -func (o *Dinosaur) HasKind() bool { - if o != nil && !IsNil(o.Kind) { - return true - } - - return false -} - -// SetKind gets a reference to the given string and assigns it to the Kind field. -func (o *Dinosaur) SetKind(v string) { - o.Kind = &v -} - -// GetHref returns the Href field value if set, zero value otherwise. -func (o *Dinosaur) GetHref() string { - if o == nil || IsNil(o.Href) { - var ret string - return ret - } - return *o.Href -} - -// GetHrefOk returns a tuple with the Href field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Dinosaur) GetHrefOk() (*string, bool) { - if o == nil || IsNil(o.Href) { - return nil, false - } - return o.Href, true -} - -// HasHref returns a boolean if a field has been set. -func (o *Dinosaur) HasHref() bool { - if o != nil && !IsNil(o.Href) { - return true - } - - return false -} - -// SetHref gets a reference to the given string and assigns it to the Href field. -func (o *Dinosaur) SetHref(v string) { - o.Href = &v -} - -// GetCreatedAt returns the CreatedAt field value if set, zero value otherwise. -func (o *Dinosaur) GetCreatedAt() time.Time { - if o == nil || IsNil(o.CreatedAt) { - var ret time.Time - return ret - } - return *o.CreatedAt -} - -// GetCreatedAtOk returns a tuple with the CreatedAt field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Dinosaur) GetCreatedAtOk() (*time.Time, bool) { - if o == nil || IsNil(o.CreatedAt) { - return nil, false - } - return o.CreatedAt, true -} - -// HasCreatedAt returns a boolean if a field has been set. -func (o *Dinosaur) HasCreatedAt() bool { - if o != nil && !IsNil(o.CreatedAt) { - return true - } - - return false -} - -// SetCreatedAt gets a reference to the given time.Time and assigns it to the CreatedAt field. -func (o *Dinosaur) SetCreatedAt(v time.Time) { - o.CreatedAt = &v -} - -// GetUpdatedAt returns the UpdatedAt field value if set, zero value otherwise. -func (o *Dinosaur) GetUpdatedAt() time.Time { - if o == nil || IsNil(o.UpdatedAt) { - var ret time.Time - return ret - } - return *o.UpdatedAt -} - -// GetUpdatedAtOk returns a tuple with the UpdatedAt field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Dinosaur) GetUpdatedAtOk() (*time.Time, bool) { - if o == nil || IsNil(o.UpdatedAt) { - return nil, false - } - return o.UpdatedAt, true -} - -// HasUpdatedAt returns a boolean if a field has been set. -func (o *Dinosaur) HasUpdatedAt() bool { - if o != nil && !IsNil(o.UpdatedAt) { - return true - } - - return false -} - -// SetUpdatedAt gets a reference to the given time.Time and assigns it to the UpdatedAt field. -func (o *Dinosaur) SetUpdatedAt(v time.Time) { - o.UpdatedAt = &v -} - -// GetSpecies returns the Species field value -func (o *Dinosaur) GetSpecies() string { - if o == nil { - var ret string - return ret - } - - return o.Species -} - -// GetSpeciesOk returns a tuple with the Species field value -// and a boolean to check if the value has been set. -func (o *Dinosaur) GetSpeciesOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Species, true -} - -// SetSpecies sets field value -func (o *Dinosaur) SetSpecies(v string) { - o.Species = v -} - -func (o Dinosaur) MarshalJSON() ([]byte, error) { - toSerialize, err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o Dinosaur) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - if !IsNil(o.Id) { - toSerialize["id"] = o.Id - } - if !IsNil(o.Kind) { - toSerialize["kind"] = o.Kind - } - if !IsNil(o.Href) { - toSerialize["href"] = o.Href - } - if !IsNil(o.CreatedAt) { - toSerialize["created_at"] = o.CreatedAt - } - if !IsNil(o.UpdatedAt) { - toSerialize["updated_at"] = o.UpdatedAt - } - toSerialize["species"] = o.Species - return toSerialize, nil -} - -func (o *Dinosaur) UnmarshalJSON(data []byte) (err error) { - // This validates that all required properties are included in the JSON object - // by unmarshalling the object into a generic map with string keys and checking - // that every required field exists as a key in the generic map. - requiredProperties := []string{ - "species", - } - - allProperties := make(map[string]interface{}) - - err = json.Unmarshal(data, &allProperties) - - if err != nil { - return err - } - - for _, requiredProperty := range requiredProperties { - if _, exists := allProperties[requiredProperty]; !exists { - return fmt.Errorf("no value given for required property %v", requiredProperty) - } - } - - varDinosaur := _Dinosaur{} - - decoder := json.NewDecoder(bytes.NewReader(data)) - decoder.DisallowUnknownFields() - err = decoder.Decode(&varDinosaur) - - if err != nil { - return err - } - - *o = Dinosaur(varDinosaur) - - return err -} - -type NullableDinosaur struct { - value *Dinosaur - isSet bool -} - -func (v NullableDinosaur) Get() *Dinosaur { - return v.value -} - -func (v *NullableDinosaur) Set(val *Dinosaur) { - v.value = val - v.isSet = true -} - -func (v NullableDinosaur) IsSet() bool { - return v.isSet -} - -func (v *NullableDinosaur) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableDinosaur(val *Dinosaur) *NullableDinosaur { - return &NullableDinosaur{value: val, isSet: true} -} - -func (v NullableDinosaur) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableDinosaur) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} diff --git a/pkg/api/openapi/model_dinosaur_list.go b/pkg/api/openapi/model_dinosaur_list.go deleted file mode 100644 index db260ea..0000000 --- a/pkg/api/openapi/model_dinosaur_list.go +++ /dev/null @@ -1,268 +0,0 @@ -/* -registry-credentials-service Service API - -registry-credentials-service Service API - -API version: 0.0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package openapi - -import ( - "bytes" - "encoding/json" - "fmt" -) - -// checks if the DinosaurList type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &DinosaurList{} - -// DinosaurList struct for DinosaurList -type DinosaurList struct { - Kind string `json:"kind"` - Page int32 `json:"page"` - Size int32 `json:"size"` - Total int32 `json:"total"` - Items []Dinosaur `json:"items"` -} - -type _DinosaurList DinosaurList - -// NewDinosaurList instantiates a new DinosaurList object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewDinosaurList(kind string, page int32, size int32, total int32, items []Dinosaur) *DinosaurList { - this := DinosaurList{} - this.Kind = kind - this.Page = page - this.Size = size - this.Total = total - this.Items = items - return &this -} - -// NewDinosaurListWithDefaults instantiates a new DinosaurList object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewDinosaurListWithDefaults() *DinosaurList { - this := DinosaurList{} - return &this -} - -// GetKind returns the Kind field value -func (o *DinosaurList) GetKind() string { - if o == nil { - var ret string - return ret - } - - return o.Kind -} - -// GetKindOk returns a tuple with the Kind field value -// and a boolean to check if the value has been set. -func (o *DinosaurList) GetKindOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Kind, true -} - -// SetKind sets field value -func (o *DinosaurList) SetKind(v string) { - o.Kind = v -} - -// GetPage returns the Page field value -func (o *DinosaurList) GetPage() int32 { - if o == nil { - var ret int32 - return ret - } - - return o.Page -} - -// GetPageOk returns a tuple with the Page field value -// and a boolean to check if the value has been set. -func (o *DinosaurList) GetPageOk() (*int32, bool) { - if o == nil { - return nil, false - } - return &o.Page, true -} - -// SetPage sets field value -func (o *DinosaurList) SetPage(v int32) { - o.Page = v -} - -// GetSize returns the Size field value -func (o *DinosaurList) GetSize() int32 { - if o == nil { - var ret int32 - return ret - } - - return o.Size -} - -// GetSizeOk returns a tuple with the Size field value -// and a boolean to check if the value has been set. -func (o *DinosaurList) GetSizeOk() (*int32, bool) { - if o == nil { - return nil, false - } - return &o.Size, true -} - -// SetSize sets field value -func (o *DinosaurList) SetSize(v int32) { - o.Size = v -} - -// GetTotal returns the Total field value -func (o *DinosaurList) GetTotal() int32 { - if o == nil { - var ret int32 - return ret - } - - return o.Total -} - -// GetTotalOk returns a tuple with the Total field value -// and a boolean to check if the value has been set. -func (o *DinosaurList) GetTotalOk() (*int32, bool) { - if o == nil { - return nil, false - } - return &o.Total, true -} - -// SetTotal sets field value -func (o *DinosaurList) SetTotal(v int32) { - o.Total = v -} - -// GetItems returns the Items field value -func (o *DinosaurList) GetItems() []Dinosaur { - if o == nil { - var ret []Dinosaur - return ret - } - - return o.Items -} - -// GetItemsOk returns a tuple with the Items field value -// and a boolean to check if the value has been set. -func (o *DinosaurList) GetItemsOk() ([]Dinosaur, bool) { - if o == nil { - return nil, false - } - return o.Items, true -} - -// SetItems sets field value -func (o *DinosaurList) SetItems(v []Dinosaur) { - o.Items = v -} - -func (o DinosaurList) MarshalJSON() ([]byte, error) { - toSerialize, err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o DinosaurList) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - toSerialize["kind"] = o.Kind - toSerialize["page"] = o.Page - toSerialize["size"] = o.Size - toSerialize["total"] = o.Total - toSerialize["items"] = o.Items - return toSerialize, nil -} - -func (o *DinosaurList) UnmarshalJSON(data []byte) (err error) { - // This validates that all required properties are included in the JSON object - // by unmarshalling the object into a generic map with string keys and checking - // that every required field exists as a key in the generic map. - requiredProperties := []string{ - "kind", - "page", - "size", - "total", - "items", - } - - allProperties := make(map[string]interface{}) - - err = json.Unmarshal(data, &allProperties) - - if err != nil { - return err - } - - for _, requiredProperty := range requiredProperties { - if _, exists := allProperties[requiredProperty]; !exists { - return fmt.Errorf("no value given for required property %v", requiredProperty) - } - } - - varDinosaurList := _DinosaurList{} - - decoder := json.NewDecoder(bytes.NewReader(data)) - decoder.DisallowUnknownFields() - err = decoder.Decode(&varDinosaurList) - - if err != nil { - return err - } - - *o = DinosaurList(varDinosaurList) - - return err -} - -type NullableDinosaurList struct { - value *DinosaurList - isSet bool -} - -func (v NullableDinosaurList) Get() *DinosaurList { - return v.value -} - -func (v *NullableDinosaurList) Set(val *DinosaurList) { - v.value = val - v.isSet = true -} - -func (v NullableDinosaurList) IsSet() bool { - return v.isSet -} - -func (v *NullableDinosaurList) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableDinosaurList(val *DinosaurList) *NullableDinosaurList { - return &NullableDinosaurList{value: val, isSet: true} -} - -func (v NullableDinosaurList) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableDinosaurList) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} diff --git a/pkg/api/openapi/model_dinosaur_patch_request.go b/pkg/api/openapi/model_dinosaur_patch_request.go deleted file mode 100644 index 386a8e1..0000000 --- a/pkg/api/openapi/model_dinosaur_patch_request.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -registry-credentials-service Service API - -registry-credentials-service Service API - -API version: 0.0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package openapi - -import ( - "encoding/json" -) - -// checks if the DinosaurPatchRequest type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &DinosaurPatchRequest{} - -// DinosaurPatchRequest struct for DinosaurPatchRequest -type DinosaurPatchRequest struct { - Species *string `json:"species,omitempty"` -} - -// NewDinosaurPatchRequest instantiates a new DinosaurPatchRequest object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewDinosaurPatchRequest() *DinosaurPatchRequest { - this := DinosaurPatchRequest{} - return &this -} - -// NewDinosaurPatchRequestWithDefaults instantiates a new DinosaurPatchRequest object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewDinosaurPatchRequestWithDefaults() *DinosaurPatchRequest { - this := DinosaurPatchRequest{} - return &this -} - -// GetSpecies returns the Species field value if set, zero value otherwise. -func (o *DinosaurPatchRequest) GetSpecies() string { - if o == nil || IsNil(o.Species) { - var ret string - return ret - } - return *o.Species -} - -// GetSpeciesOk returns a tuple with the Species field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *DinosaurPatchRequest) GetSpeciesOk() (*string, bool) { - if o == nil || IsNil(o.Species) { - return nil, false - } - return o.Species, true -} - -// HasSpecies returns a boolean if a field has been set. -func (o *DinosaurPatchRequest) HasSpecies() bool { - if o != nil && !IsNil(o.Species) { - return true - } - - return false -} - -// SetSpecies gets a reference to the given string and assigns it to the Species field. -func (o *DinosaurPatchRequest) SetSpecies(v string) { - o.Species = &v -} - -func (o DinosaurPatchRequest) MarshalJSON() ([]byte, error) { - toSerialize, err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o DinosaurPatchRequest) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - if !IsNil(o.Species) { - toSerialize["species"] = o.Species - } - return toSerialize, nil -} - -type NullableDinosaurPatchRequest struct { - value *DinosaurPatchRequest - isSet bool -} - -func (v NullableDinosaurPatchRequest) Get() *DinosaurPatchRequest { - return v.value -} - -func (v *NullableDinosaurPatchRequest) Set(val *DinosaurPatchRequest) { - v.value = val - v.isSet = true -} - -func (v NullableDinosaurPatchRequest) IsSet() bool { - return v.isSet -} - -func (v *NullableDinosaurPatchRequest) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableDinosaurPatchRequest(val *DinosaurPatchRequest) *NullableDinosaurPatchRequest { - return &NullableDinosaurPatchRequest{value: val, isSet: true} -} - -func (v NullableDinosaurPatchRequest) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableDinosaurPatchRequest) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} diff --git a/pkg/api/openapi_embed.go b/pkg/api/openapi_embed.go index e3f69f3..289168d 100755 --- a/pkg/api/openapi_embed.go +++ b/pkg/api/openapi_embed.go @@ -12,4 +12,3 @@ var openapiFS embed.FS func GetOpenAPISpec() ([]byte, error) { return fs.ReadFile(openapiFS, "openapi/api/openapi.yaml") } - diff --git a/pkg/api/presenters/accessToken.go b/pkg/api/presenters/accessToken.go deleted file mode 100644 index 6dff2d1..0000000 --- a/pkg/api/presenters/accessToken.go +++ /dev/null @@ -1,33 +0,0 @@ -package presenters - -import ( - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/util" -) - -func ConvertAccessToken(accessToken openapi.AccessToken) *api.AccessToken { - c := &api.AccessToken{ - Meta: api.Meta{ - ID: util.NilToEmptyString(accessToken.Id), - }, - } - - if accessToken.CreatedAt != nil { - c.CreatedAt = *accessToken.CreatedAt - c.UpdatedAt = *accessToken.UpdatedAt - } - - return c -} - -func PresentAccessToken(accessToken *api.AccessToken) openapi.AccessToken { - reference := PresentReference(accessToken.ID, accessToken) - return openapi.AccessToken{ - Id: reference.Id, - Kind: reference.Kind, - Href: reference.Href, - CreatedAt: openapi.PtrTime(accessToken.CreatedAt), - UpdatedAt: openapi.PtrTime(accessToken.UpdatedAt), - } -} diff --git a/pkg/api/presenters/dinosaur.go b/pkg/api/presenters/dinosaur.go deleted file mode 100755 index 302230d..0000000 --- a/pkg/api/presenters/dinosaur.go +++ /dev/null @@ -1,28 +0,0 @@ -package presenters - -import ( - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/util" -) - -func ConvertDinosaur(dinosaur openapi.Dinosaur) *api.Dinosaur { - return &api.Dinosaur{ - Meta: api.Meta{ - ID: util.NilToEmptyString(dinosaur.Id), - }, - Species: dinosaur.Species, - } -} - -func PresentDinosaur(dinosaur *api.Dinosaur) openapi.Dinosaur { - reference := PresentReference(dinosaur.ID, dinosaur) - return openapi.Dinosaur{ - Id: reference.Id, - Kind: reference.Kind, - Href: reference.Href, - Species: dinosaur.Species, - CreatedAt: openapi.PtrTime(dinosaur.CreatedAt), - UpdatedAt: openapi.PtrTime(dinosaur.UpdatedAt), - } -} diff --git a/pkg/api/presenters/error.go b/pkg/api/presenters/error.go deleted file mode 100755 index f1b215e..0000000 --- a/pkg/api/presenters/error.go +++ /dev/null @@ -1,10 +0,0 @@ -package presenters - -import ( - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -func PresentError(err *errors.ServiceError) openapi.Error { - return err.AsOpenapiError("") -} diff --git a/pkg/api/presenters/kind.go b/pkg/api/presenters/kind.go deleted file mode 100755 index 7a34444..0000000 --- a/pkg/api/presenters/kind.go +++ /dev/null @@ -1,44 +0,0 @@ -package presenters - -import ( - "fmt" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -type KindMappingFunc func(interface{}) string - -var kindRegistry = make(map[string]KindMappingFunc) - -func RegisterKind(objType interface{}, kindValue string) { - typeName := fmt.Sprintf("%T", objType) - kindRegistry[typeName] = func(interface{}) string { - return kindValue - } -} - -func LoadDiscoveredKinds(i interface{}) string { - typeName := fmt.Sprintf("%T", i) - if mappingFunc, found := kindRegistry[typeName]; found { - return mappingFunc(i) - } - return "" -} - -func ObjectKind(i interface{}) *string { - result := "" - - // Check auto-discovered kinds first - if discoveredKind := LoadDiscoveredKinds(i); discoveredKind != "" { - result = discoveredKind - } else { - // Built-in mappings - switch i.(type) { - case errors.ServiceError, *errors.ServiceError: - result = "Error" - } - } - - return openapi.PtrString(result) -} diff --git a/pkg/api/presenters/object_reference.go b/pkg/api/presenters/object_reference.go deleted file mode 100755 index dd3dd74..0000000 --- a/pkg/api/presenters/object_reference.go +++ /dev/null @@ -1,35 +0,0 @@ -package presenters - -import ( - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" -) - -func PresentReference(id, obj interface{}) openapi.ObjectReference { - refId, ok := makeReferenceId(id) - - if !ok { - return openapi.ObjectReference{} - } - - return openapi.ObjectReference{ - Id: openapi.PtrString(refId), - Kind: ObjectKind(obj), - Href: ObjectPath(refId, obj), - } -} - -func makeReferenceId(id interface{}) (string, bool) { - var refId string - - if i, ok := id.(string); ok { - refId = i - } - - if i, ok := id.(*string); ok { - if i != nil { - refId = *i - } - } - - return refId, refId != "" -} diff --git a/pkg/api/presenters/path.go b/pkg/api/presenters/path.go deleted file mode 100755 index f9e886e..0000000 --- a/pkg/api/presenters/path.go +++ /dev/null @@ -1,50 +0,0 @@ -package presenters - -import ( - "fmt" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -type PathMappingFunc func(interface{}) string - -var pathRegistry = make(map[string]PathMappingFunc) - -func RegisterPath(objType interface{}, pathValue string) { - typeName := fmt.Sprintf("%T", objType) - pathRegistry[typeName] = func(interface{}) string { - return pathValue - } -} - -func LoadDiscoveredPaths(i interface{}) string { - typeName := fmt.Sprintf("%T", i) - if mappingFunc, found := pathRegistry[typeName]; found { - return mappingFunc(i) - } - return "" -} - -const ( - BasePath = "/api/registry-credential-service/v1" -) - -func ObjectPath(id string, obj interface{}) *string { - return openapi.PtrString(fmt.Sprintf("%s/%s/%s", BasePath, path(obj), id)) -} - -func path(i interface{}) string { - // Check auto-discovered paths first - if discoveredPath := LoadDiscoveredPaths(i); discoveredPath != "" { - return discoveredPath - } - - // Built-in mappings - switch i.(type) { - case errors.ServiceError, *errors.ServiceError: - return "errors" - default: - return "" - } -} diff --git a/pkg/api/presenters/registry.go b/pkg/api/presenters/registry.go deleted file mode 100644 index 99b0467..0000000 --- a/pkg/api/presenters/registry.go +++ /dev/null @@ -1,33 +0,0 @@ -package presenters - -import ( - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/util" -) - -func ConvertRegistry(registry openapi.Registry) *api.Registry { - c := &api.Registry{ - Meta: api.Meta{ - ID: util.NilToEmptyString(registry.Id), - }, - } - - if registry.CreatedAt != nil { - c.CreatedAt = *registry.CreatedAt - c.UpdatedAt = *registry.UpdatedAt - } - - return c -} - -func PresentRegistry(registry *api.Registry) openapi.Registry { - reference := PresentReference(registry.ID, registry) - return openapi.Registry{ - Id: reference.Id, - Kind: reference.Kind, - Href: reference.Href, - CreatedAt: openapi.PtrTime(registry.CreatedAt), - UpdatedAt: openapi.PtrTime(registry.UpdatedAt), - } -} diff --git a/pkg/api/presenters/slice_filter.go b/pkg/api/presenters/slice_filter.go deleted file mode 100755 index 7681259..0000000 --- a/pkg/api/presenters/slice_filter.go +++ /dev/null @@ -1,232 +0,0 @@ -package presenters - -import ( - "fmt" - "reflect" - "regexp" - "strings" - "time" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -type ProjectionList struct { - Kind string `json:"kind"` - Page int32 `json:"page"` - Size int32 `json:"size"` - Total int32 `json:"total"` - Items []map[string]interface{} `json:"items"` -} - -/* - SliceFilter - -Convert slice of structures to a []byte stream. -Non-existing fields will cause a validation error - -@param fields2Store []string - list of fields to export (from `json` tag) - -@param items []interface{} - slice of structures to export - -@param kind, page, size, total - from openapi.SubscriptionList et al. - -@return []byte -*/ -func SliceFilter(fields2Store []string, model interface{}) (*ProjectionList, *errors.ServiceError) { - if model == nil { - return nil, errors.Validation("Empty model") - } - - // Prepare list of required field - var in = map[string]bool{} - for i := 0; i < len(fields2Store); i++ { - in[fields2Store[i]] = true - } - - reflectValue := reflect.ValueOf(model) - reflectValue = reflect.Indirect(reflectValue) - - // Initialize result structure - result := &ProjectionList{ - Kind: reflectValue.FieldByName("Kind").String(), - Page: int32(reflectValue.FieldByName("Page").Int()), - Size: int32(reflectValue.FieldByName("Size").Int()), - Total: int32(reflectValue.FieldByName("Total").Int()), - Items: nil, - } - - field := reflectValue.FieldByName("Items").Interface() - items := reflect.ValueOf(field) - if items.Len() == 0 { - return result, nil - } - - // Validate model - validateIn := make(map[string]bool) - for key, value := range in { - validateIn[key] = value - } - if err := validate(items.Index(0).Interface(), validateIn, ""); err != nil { - return nil, err - } - - // Convert items - for i := 0; i < items.Len(); i++ { - result.Items = append(result.Items, structToMap(items.Index(i).Interface(), in, "")) - } - return result, nil -} - -func validate(model interface{}, in map[string]bool, prefix string) *errors.ServiceError { - if model == nil { - return errors.Validation("Empty model") - } - - v := reflect.TypeOf(model) - reflectValue := reflect.ValueOf(model) - reflectValue = reflect.Indirect(reflectValue) - - if v.Kind() == reflect.Pointer { - v = v.Elem() - } - - for i := 0; i < v.NumField(); i++ { - t := v.Field(i) - tag := t.Tag.Get("json") - if tag == "" || tag == "-" { - continue - } - ttype := reflectValue.Field(i) - kind := ttype.Kind() - if kind == reflect.Pointer { - kind = ttype.Elem().Kind() - } - field := reflectValue.Field(i).Interface() - name := strings.Split(tag, ",")[0] - if kind == reflect.Struct { - if t.Type == reflect.TypeOf(&time.Time{}) { - delete(in, name) - } else { - star := name + ".*" - if _, ok := in[star]; ok { - in = removeStar(in, name) - } else { - _ = validate(field, in, name) - } - } - } else if t.Type.Kind() == reflect.Slice { - // TODO: We don't support Slices' validation :( - in = removeStar(in, name) - continue - //_ = validate(slice, in, name) - } else { - prefixedName := name - if prefix != "" { - prefixedName = fmt.Sprintf("%s.%s", prefix, name) - } - delete(in, prefixedName) - } - } - - // All fields present in data struct - if len(in) == 0 { - return nil - } - - var fields []string - for k := range in { - fields = append(fields, k) - } - message := fmt.Sprintf("The following field(s) doesn't exist in `%s`: %s", - reflect.TypeOf(model).Name(), strings.Join(fields, ", ")) - return errors.Validation("%s", message) -} - -func removeStar(in map[string]bool, name string) map[string]bool { - pattern := `(` + name + `\..*)` - pat, _ := regexp.Compile(pattern) - for k := range in { - matched := pat.FindAllString(k, -1) - for _, m := range matched { - delete(in, m) - } - } - - return in -} - -func structToMap(item interface{}, in map[string]bool, prefix string) map[string]interface{} { - res := map[string]interface{}{} - - if item == nil { - return res - } - v := reflect.TypeOf(item) - reflectValue := reflect.ValueOf(item) - reflectValue = reflect.Indirect(reflectValue) - - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - - for i := 0; i < v.NumField(); i++ { - t := v.Field(i) - tag := t.Tag.Get("json") - if tag == "" || tag == "-" { - continue - } - ttype := reflectValue.Field(i) - kind := ttype.Kind() - if kind == reflect.Pointer { - kind = ttype.Elem().Kind() - } - field := reflectValue.Field(i).Interface() - name := strings.Split(tag, ",")[0] - if kind == reflect.Struct { - if t.Type == reflect.TypeOf(&time.Time{}) { - if _, ok := in[name]; ok { - res[name] = field.(*time.Time).Format(time.RFC3339) - } - } else { - nexPrefix := name - if prefix != "" { - nexPrefix = prefix + "." + name - } - subStruct := structToMap(field, in, nexPrefix) - if len(subStruct) > 0 { - res[name] = subStruct - } - } - } else if kind == reflect.Slice { - s := reflect.ValueOf(field) - if s.Len() > 0 { - result := make([]interface{}, 0, s.Len()) - for i := 0; i < s.Len(); i++ { - slice := structToMap(s.Index(i).Interface(), in, name) - if len(slice) == 0 { - break - } - result = append(result, slice) - } - if len(result) > 0 { - res[name] = result - } - } - } else { - prefixedName := name - if prefix != "" { - prefixedName = fmt.Sprintf("%s.%s", prefix, name) - } - if _, ok := in[prefixedName]; ok { - res[name] = field - } else { - prefixedStar := fmt.Sprintf("%s.*", prefix) - if _, ok := in[prefixedStar]; ok { - res[name] = field - } - } - } - } - - return res -} diff --git a/pkg/api/presenters/time.go b/pkg/api/presenters/time.go deleted file mode 100755 index 917a80e..0000000 --- a/pkg/api/presenters/time.go +++ /dev/null @@ -1,14 +0,0 @@ -package presenters - -import ( - "time" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/util" -) - -func PresentTime(t time.Time) *time.Time { - if t.IsZero() { - return util.ToPtr(time.Time{}) - } - return util.ToPtr(t.Round(time.Microsecond)) -} diff --git a/pkg/api/resource_id.go b/pkg/api/resource_id.go deleted file mode 100755 index cdd71af..0000000 --- a/pkg/api/resource_id.go +++ /dev/null @@ -1,7 +0,0 @@ -package api - -import "github.com/segmentio/ksuid" - -func NewID() string { - return ksuid.New().String() -} diff --git a/pkg/api/version.go b/pkg/api/version.go deleted file mode 100755 index 8eb0e1d..0000000 --- a/pkg/api/version.go +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright (c) 2018 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// This file contains the version information that is set at build time. - -package api - -// Version is the application version set at compile time via ldflags -var Version = "unknown" - -// BuildTime is the time when the binary was built, set at compile time via ldflags -var BuildTime = "unknown" diff --git a/pkg/auth/auth_middleware.go b/pkg/auth/auth_middleware.go deleted file mode 100755 index 0c4733b..0000000 --- a/pkg/auth/auth_middleware.go +++ /dev/null @@ -1,47 +0,0 @@ -package auth - -import ( - "fmt" - "net/http" - - "github.com/getsentry/sentry-go" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -type JWTMiddleware interface { - AuthenticateAccountJWT(next http.Handler) http.Handler -} - -type Middleware struct{} - -var _ JWTMiddleware = &Middleware{} - -func NewAuthMiddleware() (*Middleware, error) { - middleware := Middleware{} - return &middleware, nil -} - -// AuthenticateAccountJWT Middleware handler to validate JWT tokens and authenticate users -func (a *Middleware) AuthenticateAccountJWT(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - payload, err := GetAuthPayload(r) - if err != nil { - handleError(ctx, w, errors.ErrorUnauthorized, fmt.Sprintf("Unable to get payload details from JWT token: %s", err)) - return - } - - // Append the username to the request context - ctx = SetUsernameContext(ctx, payload.Username) - *r = *r.WithContext(ctx) - - // Add username to sentry context - if hub := sentry.GetHubFromContext(ctx); hub != nil { - hub.ConfigureScope(func(scope *sentry.Scope) { - scope.SetUser(sentry.User{ID: payload.Username}) - }) - } - next.ServeHTTP(w, r) - }) -} diff --git a/pkg/auth/auth_middleware_mock.go b/pkg/auth/auth_middleware_mock.go deleted file mode 100755 index 5deef8e..0000000 --- a/pkg/auth/auth_middleware_mock.go +++ /dev/null @@ -1,16 +0,0 @@ -package auth - -import ( - "net/http" -) - -type MiddlewareMock struct{} - -var _ JWTMiddleware = &MiddlewareMock{} - -func (a *MiddlewareMock) AuthenticateAccountJWT(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // TODO need to append a username to the request context - next.ServeHTTP(w, r) - }) -} diff --git a/pkg/auth/authz_middleware.go b/pkg/auth/authz_middleware.go deleted file mode 100755 index d281444..0000000 --- a/pkg/auth/authz_middleware.go +++ /dev/null @@ -1,72 +0,0 @@ -package auth - -/* - The goal of this simple authz middlewre is to provide a way for access review - parameters to be declared for each route in a microservice. This is not meant - to handle more complex access review calls in particular scopes, but rather - just authz calls at the application scope - - This is a big TODO, not ready for consumption -*/ - -import ( - "fmt" - "net/http" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/client/ocm" -) - -type AuthorizationMiddleware interface { - AuthorizeApi(next http.Handler) http.Handler -} - -type authzMiddleware struct { - action string - resourceType string - - ocmClient *ocm.Client -} - -var _ AuthorizationMiddleware = &authzMiddleware{} - -func NewAuthzMiddleware(ocmClient *ocm.Client, action, resourceType string) AuthorizationMiddleware { - return &authzMiddleware{ - ocmClient: ocmClient, - action: action, - resourceType: resourceType, - } -} - -func (a authzMiddleware) AuthorizeApi(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - // Get username from context - username := GetUsernameFromContext(ctx) - if username == "" { - _ = fmt.Errorf("authenticated username not present in request context") - // TODO - //body := api.E500.Format(r, "Authentication details not present in context") - //api.SendError(w, r, &body) - return - } - - allowed, err := a.ocmClient.Authorization.AccessReview( - ctx, username, a.action, a.resourceType, "", "", "") - if err != nil { - _ = fmt.Errorf("unable to make authorization request: %s", err) - // TODO - //body := api.E500.Format(r, "Unable to make authorization request") - //api.SendError(w, r, &body) - return - } - - if allowed { - next.ServeHTTP(w, r) - } - - // TODO - //body := api.E403.Format(r, "") - //api.SendError(w, r, &body) - }) -} diff --git a/pkg/auth/authz_middleware_mock.go b/pkg/auth/authz_middleware_mock.go deleted file mode 100755 index 9a0a345..0000000 --- a/pkg/auth/authz_middleware_mock.go +++ /dev/null @@ -1,22 +0,0 @@ -package auth - -import ( - "net/http" - - "github.com/golang/glog" -) - -type authzMiddlewareMock struct{} - -var _ AuthorizationMiddleware = &authzMiddlewareMock{} - -func NewAuthzMiddlewareMock() AuthorizationMiddleware { - return &authzMiddlewareMock{} -} - -func (a authzMiddlewareMock) AuthorizeApi(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - glog.Infof("Mock authz allows / for %q/%q", r.Method, r.URL) - next.ServeHTTP(w, r) - }) -} diff --git a/pkg/auth/context.go b/pkg/auth/context.go deleted file mode 100755 index 2bd6294..0000000 --- a/pkg/auth/context.go +++ /dev/null @@ -1,115 +0,0 @@ -package auth - -import ( - "context" - "fmt" - "net/http" - "strings" - - "github.com/golang-jwt/jwt/v4" - "github.com/openshift-online/ocm-sdk-go/authentication" -) - -// Context key type defined to avoid collisions in other pkgs using context -// See https://golang.org/pkg/context/#WithValue -type contextKey string - -const ( - ContextUsernameKey contextKey = "username" - - // Does not use contextKey type because the jwt middleware improperly updates context with string key type - // See https://github.com/auth0/go-jwt-middleware/blob/master/jwtmiddleware.go#L232 - ContextAuthKey string = "user" -) - -// AuthPayload defines the structure of the JWT payload we expect from -// RHD JWT tokens -type Payload struct { - Username string `json:"username"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Email string `json:"email"` - Issuer string `json:"iss"` - ClientID string `json:"clientId"` -} - -func SetUsernameContext(ctx context.Context, username string) context.Context { - return context.WithValue(ctx, ContextUsernameKey, username) -} - -func GetUsernameFromContext(ctx context.Context) string { - username := ctx.Value(ContextUsernameKey) - if username == nil { - return "" - } - return username.(string) -} - -// GetAuthPayloadFromContext Get authorization payload api object from context -func GetAuthPayloadFromContext(ctx context.Context) (*Payload, error) { - // Get user token from request context and validate - userToken, err := authentication.TokenFromContext(ctx) - if err != nil { - return nil, fmt.Errorf("unable to retrieve JWT token from request context: %v", err) - } - - if userToken == nil { - return nil, fmt.Errorf("JWT token in context is nil, unauthorized") - } - - // Username is stored in token claim with key 'sub' - claims, ok := userToken.Claims.(jwt.MapClaims) - if !ok { - err := fmt.Errorf("unable to parse JWT token claims: %#v", userToken.Claims) - return nil, err - } - - // TODO figure out how to unmarshal jwt.mapclaims into the struct to avoid all the - // type assertions - // - //var accountAuth api.AuthPayload - //err := json.Unmarshal([]byte(claims), &accountAuth) - //if err != nil { - // err := fmt.Errorf("Unable to parse JWT token claims") - // return nil, err - //} - - payload := &Payload{} - // default to the values we expect from RHSSO - payload.Username, _ = claims["username"].(string) - payload.FirstName, _ = claims["first_name"].(string) - payload.LastName, _ = claims["last_name"].(string) - payload.Email, _ = claims["email"].(string) - payload.ClientID, _ = claims["clientId"].(string) - - // Check values, if empty, use alternative claims from RHD - if payload.Username == "" { - payload.Username, _ = claims["preferred_username"].(string) - } - - if payload.FirstName == "" { - payload.FirstName, _ = claims["given_name"].(string) - } - - if payload.LastName == "" { - payload.LastName, _ = claims["family_name"].(string) - } - - // If given and family names are not present, use the name field - if payload.FirstName == "" || payload.LastName == "" { - name, _ := claims["name"].(string) - names := strings.Split(name, " ") - if len(names) > 1 { - payload.FirstName = names[0] - payload.LastName = names[1] - } else { - payload.FirstName = names[0] - } - } - - return payload, nil -} - -func GetAuthPayload(r *http.Request) (*Payload, error) { - return GetAuthPayloadFromContext(r.Context()) -} diff --git a/pkg/auth/helpers.go b/pkg/auth/helpers.go deleted file mode 100755 index 8ccc27c..0000000 --- a/pkg/auth/helpers.go +++ /dev/null @@ -1,33 +0,0 @@ -package auth - -import ( - "context" - "encoding/json" - "net/http" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" -) - -func handleError(ctx context.Context, w http.ResponseWriter, code errors.ServiceErrorCode, reason string) { - log := logger.NewOCMLogger(ctx) - operationID := logger.GetOperationID(ctx) - err := errors.New(code, "%s", reason) - if err.HttpCode >= 400 && err.HttpCode <= 499 { - log.Infof(err.Error()) - } else { - log.Error(err.Error()) - } - - writeJSONResponse(w, err.HttpCode, err.AsOpenapiError(operationID)) -} - -func writeJSONResponse(w http.ResponseWriter, code int, payload interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - - if payload != nil { - response, _ := json.Marshal(payload) - _, _ = w.Write(response) - } -} diff --git a/pkg/client/ocm/authorization.go b/pkg/client/ocm/authorization.go deleted file mode 100755 index 29d3bf3..0000000 --- a/pkg/client/ocm/authorization.go +++ /dev/null @@ -1,77 +0,0 @@ -package ocm - -import ( - "context" - "fmt" - - azv1 "github.com/openshift-online/ocm-sdk-go/authorizations/v1" -) - -type Authorization interface { - SelfAccessReview(ctx context.Context, action, resourceType, organizationID, subscriptionID, clusterID string) (allowed bool, err error) - AccessReview(ctx context.Context, username, action, resourceType, organizationID, subscriptionID, clusterID string) (allowed bool, err error) -} - -type authorization service - -var _ Authorization = &authorization{} - -func (a authorization) SelfAccessReview(ctx context.Context, action, resourceType, organizationID, subscriptionID, clusterID string) (allowed bool, err error) { - con := a.client.connection - selfAccessReview := con.Authorizations().V1().SelfAccessReview() - - request, err := azv1.NewSelfAccessReviewRequest(). - Action(action). - ResourceType(resourceType). - OrganizationID(organizationID). - ClusterID(clusterID). - SubscriptionID(subscriptionID). - Build() - if err != nil { - return false, err - } - - postResp, err := selfAccessReview.Post(). - Request(request). - SendContext(ctx) - if err != nil { - return false, err - } - response, ok := postResp.GetResponse() - if !ok { - return false, fmt.Errorf("empty response from authorization post request") - } - - return response.Allowed(), nil -} - -func (a authorization) AccessReview(ctx context.Context, username, action, resourceType, organizationID, subscriptionID, clusterID string) (allowed bool, err error) { - con := a.client.connection - accessReview := con.Authorizations().V1().AccessReview() - - request, err := azv1.NewAccessReviewRequest(). - AccountUsername(username). - Action(action). - ResourceType(resourceType). - OrganizationID(organizationID). - ClusterID(clusterID). - SubscriptionID(subscriptionID). - Build() - if err != nil { - return false, err - } - - postResp, err := accessReview.Post(). - Request(request). - SendContext(ctx) - if err != nil { - return false, err - } - - response, ok := postResp.GetResponse() - if !ok { - return false, fmt.Errorf("empty response from authorization post request") - } - - return response.Allowed(), nil -} diff --git a/pkg/client/ocm/authorization_mock.go b/pkg/client/ocm/authorization_mock.go deleted file mode 100755 index e5ee369..0000000 --- a/pkg/client/ocm/authorization_mock.go +++ /dev/null @@ -1,18 +0,0 @@ -package ocm - -import ( - "context" -) - -// authorizationMock returns allowed=true for every request -type authorizationMock service - -var _ Authorization = &authorizationMock{} - -func (a authorizationMock) SelfAccessReview(ctx context.Context, action, resourceType, organizationID, subscriptionID, clusterID string) (allowed bool, err error) { - return true, nil -} - -func (a authorizationMock) AccessReview(ctx context.Context, username, action, resourceType, organizationID, subscriptionID, clusterID string) (allowed bool, err error) { - return true, nil -} diff --git a/pkg/client/ocm/client.go b/pkg/client/ocm/client.go deleted file mode 100755 index bcdc895..0000000 --- a/pkg/client/ocm/client.go +++ /dev/null @@ -1,86 +0,0 @@ -package ocm - -import ( - "fmt" - - sdkClient "github.com/openshift-online/ocm-sdk-go" -) - -type Client struct { - config *Config - logger sdkClient.Logger - connection *sdkClient.Connection - - Authorization Authorization -} - -type Config struct { - BaseURL string - ClientID string - ClientSecret string - SelfToken string - TokenURL string - Debug bool -} - -func NewClient(config Config) (*Client, error) { - // Create a logger that has the debug level enabled: - logger, err := sdkClient.NewGoLoggerBuilder(). - Debug(config.Debug). - Build() - if err != nil { - return nil, fmt.Errorf("unable to build OCM logger: %s", err.Error()) - } - - client := &Client{ - config: &config, - logger: logger, - } - err = client.newConnection() - if err != nil { - return nil, fmt.Errorf("unable to build OCM connection: %s", err.Error()) - } - client.Authorization = &authorization{client: client} - return client, nil -} - -func NewClientMock(config Config) (*Client, error) { - client := &Client{ - config: &config, - } - client.Authorization = &authorizationMock{client: client} - return client, nil -} - -func (c *Client) newConnection() error { - builder := sdkClient.NewConnectionBuilder(). - Logger(c.logger). - URL(c.config.BaseURL). - MetricsSubsystem("api_outbound") - - if c.config.ClientID != "" && c.config.ClientSecret != "" { - builder = builder.Client(c.config.ClientID, c.config.ClientSecret) - } else if c.config.SelfToken != "" { - builder = builder.Tokens(c.config.SelfToken) - } else { - return fmt.Errorf("can't build OCM client connection: no Client/Secret or Token has been provided") - } - - connection, err := builder.Build() - - if err != nil { - return fmt.Errorf("can't build OCM client connection: %s", err.Error()) - } - c.connection = connection - return nil -} - -func (c *Client) Close() { - if c.connection != nil { - c.connection.Close() - } -} - -type service struct { - client *Client -} diff --git a/pkg/config/config.go b/pkg/config/config.go deleted file mode 100755 index d3d341c..0000000 --- a/pkg/config/config.go +++ /dev/null @@ -1,132 +0,0 @@ -package config - -import ( - "flag" - "fmt" - "os" - "path/filepath" - "runtime" - "strconv" - "strings" - - "github.com/spf13/pflag" -) - -type ApplicationConfig struct { - Server *ServerConfig `json:"server"` - Metrics *MetricsConfig `json:"metrics"` - HealthCheck *HealthCheckConfig `json:"health_check"` - Database *DatabaseConfig `json:"database"` - OCM *OCMConfig `json:"ocm"` - Sentry *SentryConfig `json:"sentry"` -} - -func NewApplicationConfig() *ApplicationConfig { - return &ApplicationConfig{ - Server: NewServerConfig(), - Metrics: NewMetricsConfig(), - HealthCheck: NewHealthCheckConfig(), - Database: NewDatabaseConfig(), - OCM: NewOCMConfig(), - Sentry: NewSentryConfig(), - } -} - -func (c *ApplicationConfig) AddFlags(flagset *pflag.FlagSet) { - flagset.AddGoFlagSet(flag.CommandLine) - c.Server.AddFlags(flagset) - c.Metrics.AddFlags(flagset) - c.HealthCheck.AddFlags(flagset) - c.Database.AddFlags(flagset) - c.OCM.AddFlags(flagset) - c.Sentry.AddFlags(flagset) -} - -func (c *ApplicationConfig) ReadFiles() []string { - readFiles := []struct { - f func() error - name string - }{ - {c.Server.ReadFiles, "Server"}, - {c.Database.ReadFiles, "Database"}, - {c.OCM.ReadFiles, "OCM"}, - {c.Metrics.ReadFiles, "Metrics"}, - {c.HealthCheck.ReadFiles, "HealthCheck"}, - {c.Sentry.ReadFiles, "Sentry"}, - } - var messages []string - for _, rf := range readFiles { - if err := rf.f(); err != nil { - msg := fmt.Sprintf("%s %s", rf.name, err.Error()) - messages = append(messages, msg) - } - } - return messages -} - -// Read the contents of file into integer value -func readFileValueInt(file string, val *int) error { - fileContents, err := ReadFile(file) - if err != nil { - return err - } - - *val, err = strconv.Atoi(fileContents) - return err -} - -// Read the contents of file into string value -func readFileValueString(file string, val *string) error { - fileContents, err := ReadFile(file) - if err != nil { - return err - } - - *val = strings.TrimSuffix(fileContents, "\n") - return err -} - -// Read the contents of file into boolean value -func readFileValueBool(file string, val *bool) error { - fileContents, err := ReadFile(file) - if err != nil { - return err - } - - *val, err = strconv.ParseBool(fileContents) - return err -} - -func ReadFile(file string) (string, error) { - // If the value is in quotes, unquote it - unquotedFile, err := strconv.Unquote(file) - if err != nil { - // values without quotes will raise an error, ignore it. - unquotedFile = file - } - - // If no file is provided, leave val unchanged. - if unquotedFile == "" { - return "", nil - } - - // Ensure the absolute file path is used - absFilePath := unquotedFile - if !filepath.IsAbs(unquotedFile) { - absFilePath = filepath.Join(GetProjectRootDir(), unquotedFile) - } - - // Read the file - buf, err := os.ReadFile(absFilePath) - if err != nil { - return "", err - } - return string(buf), nil -} - -// GetProjectRootDir Return project root path based on the relative path of this file -func GetProjectRootDir() string { - _, b, _, _ := runtime.Caller(0) - basepath := filepath.Dir(filepath.Join(b, "..", "..")) - return basepath -} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go deleted file mode 100755 index 453f2ab..0000000 --- a/pkg/config/config_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package config - -import ( - "log" - "os" - "testing" - - . "github.com/onsi/gomega" -) - -func TestConfigReadStringFile(t *testing.T) { - RegisterTestingT(t) - - stringFile, err := createConfigFile("string", "example\n") - defer os.Remove(stringFile.Name()) - if err != nil { - log.Fatal(err) - } - - var stringConfig string - err = readFileValueString(stringFile.Name(), &stringConfig) - Expect(err).NotTo(HaveOccurred()) - Expect(stringConfig).To(Equal("example")) -} - -func TestConfigReadIntFile(t *testing.T) { - RegisterTestingT(t) - - intFile, err := createConfigFile("int", "123") - defer os.Remove(intFile.Name()) - if err != nil { - log.Fatal(err) - } - - var intConfig int - err = readFileValueInt(intFile.Name(), &intConfig) - Expect(err).NotTo(HaveOccurred()) - Expect(intConfig).To(Equal(123)) -} - -func TestConfigReadBoolFile(t *testing.T) { - RegisterTestingT(t) - - boolFile, err := createConfigFile("bool", "true") - defer os.Remove(boolFile.Name()) - if err != nil { - log.Fatal(err) - } - - var boolConfig = false - err = readFileValueBool(boolFile.Name(), &boolConfig) - Expect(err).NotTo(HaveOccurred()) - Expect(boolConfig).To(Equal(true)) -} - -func TestConfigReadQuotedFile(t *testing.T) { - RegisterTestingT(t) - - stringFile, err := createConfigFile("string", "example") - defer os.Remove(stringFile.Name()) - if err != nil { - log.Fatal(err) - } - - quotedFileName := "\"" + stringFile.Name() + "\"" - val, err := ReadFile(quotedFileName) - Expect(err).NotTo(HaveOccurred()) - Expect(val).To(Equal("example")) -} -func createConfigFile(namePrefix, contents string) (*os.File, error) { - configFile, err := os.CreateTemp("", namePrefix) - if err != nil { - return nil, err - } - if _, err = configFile.Write([]byte(contents)); err != nil { - return configFile, err - } - err = configFile.Close() - return configFile, err -} diff --git a/pkg/config/db.go b/pkg/config/db.go deleted file mode 100755 index 65c95ae..0000000 --- a/pkg/config/db.go +++ /dev/null @@ -1,119 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/spf13/pflag" -) - -type DatabaseConfig struct { - Dialect string `json:"dialect"` - SSLMode string `json:"sslmode"` - Debug bool `json:"debug"` - MaxOpenConnections int `json:"max_connections"` - - Host string `json:"host"` - Port int `json:"port"` - Name string `json:"name"` - Username string `json:"username"` - Password string `json:"password"` - - HostFile string `json:"host_file"` - PortFile string `json:"port_file"` - NameFile string `json:"name_file"` - UsernameFile string `json:"username_file"` - PasswordFile string `json:"password_file"` - RootCertFile string `json:"certificate_file"` -} - -func NewDatabaseConfig() *DatabaseConfig { - return &DatabaseConfig{ - Dialect: "postgres", - SSLMode: "disable", - Debug: false, - MaxOpenConnections: 50, - - HostFile: "secrets/db.host", - PortFile: "secrets/db.port", - NameFile: "secrets/db.name", - UsernameFile: "secrets/db.user", - PasswordFile: "secrets/db.password", - RootCertFile: "secrets/db.rootcert", - } -} - -func (c *DatabaseConfig) AddFlags(fs *pflag.FlagSet) { - fs.StringVar(&c.HostFile, "db-host-file", c.HostFile, "Database host string file") - fs.StringVar(&c.PortFile, "db-port-file", c.PortFile, "Database port file") - fs.StringVar(&c.UsernameFile, "db-user-file", c.UsernameFile, "Database username file") - fs.StringVar(&c.PasswordFile, "db-password-file", c.PasswordFile, "Database password file") - fs.StringVar(&c.NameFile, "db-name-file", c.NameFile, "Database name file") - fs.StringVar(&c.RootCertFile, "db-rootcert", c.RootCertFile, "Database root certificate file") - fs.StringVar(&c.SSLMode, "db-sslmode", c.SSLMode, "Database ssl mode (disable | require | verify-ca | verify-full)") - fs.BoolVar(&c.Debug, "enable-db-debug", c.Debug, " framework's debug mode") - fs.IntVar(&c.MaxOpenConnections, "db-max-open-connections", c.MaxOpenConnections, "Maximum open DB connections for this instance") -} - -func (c *DatabaseConfig) ReadFiles() error { - err := readFileValueString(c.HostFile, &c.Host) - if err != nil { - return err - } - - err = readFileValueInt(c.PortFile, &c.Port) - if err != nil { - return err - } - - err = readFileValueString(c.UsernameFile, &c.Username) - if err != nil { - return err - } - - err = readFileValueString(c.PasswordFile, &c.Password) - if err != nil { - return err - } - - err = readFileValueString(c.NameFile, &c.Name) - return err -} - -func (c *DatabaseConfig) ConnectionString(withSSL bool) string { - return c.ConnectionStringWithName(c.Name, withSSL) -} - -func (c *DatabaseConfig) ConnectionStringWithName(name string, withSSL bool) string { - var cmd string - if withSSL { - cmd = fmt.Sprintf( - "host=%s port=%d user=%s password='%s' dbname=%s sslmode=%s sslrootcert=%s", - c.Host, c.Port, c.Username, c.Password, name, c.SSLMode, c.RootCertFile, - ) - } else { - cmd = fmt.Sprintf( - "host=%s port=%d user=%s password='%s' dbname=%s sslmode=disable", - c.Host, c.Port, c.Username, c.Password, name, - ) - } - - return cmd -} - -func (c *DatabaseConfig) LogSafeConnectionString(withSSL bool) string { - return c.LogSafeConnectionStringWithName(c.Name, withSSL) -} - -func (c *DatabaseConfig) LogSafeConnectionStringWithName(name string, withSSL bool) string { - if withSSL { - return fmt.Sprintf( - "host=%s port=%d user=%s password='' dbname=%s sslmode=%s sslrootcert=''", - c.Host, c.Port, c.Username, name, c.SSLMode, - ) - } else { - return fmt.Sprintf( - "host=%s port=%d user=%s password='' dbname=%s", - c.Host, c.Port, c.Username, name, - ) - } -} diff --git a/pkg/config/health_check.go b/pkg/config/health_check.go deleted file mode 100755 index e3aaa79..0000000 --- a/pkg/config/health_check.go +++ /dev/null @@ -1,26 +0,0 @@ -package config - -import ( - "github.com/spf13/pflag" -) - -type HealthCheckConfig struct { - BindAddress string `json:"bind_address"` - EnableHTTPS bool `json:"enable_https"` -} - -func NewHealthCheckConfig() *HealthCheckConfig { - return &HealthCheckConfig{ - BindAddress: "localhost:8083", - EnableHTTPS: false, - } -} - -func (c *HealthCheckConfig) AddFlags(fs *pflag.FlagSet) { - fs.StringVar(&c.BindAddress, "health-check-server-bindaddress", c.BindAddress, "Health check server bind adddress") - fs.BoolVar(&c.EnableHTTPS, "enable-health-check-https", c.EnableHTTPS, "Enable HTTPS for health check server") -} - -func (c *HealthCheckConfig) ReadFiles() error { - return nil -} diff --git a/pkg/config/metrics.go b/pkg/config/metrics.go deleted file mode 100755 index b01c3c3..0000000 --- a/pkg/config/metrics.go +++ /dev/null @@ -1,31 +0,0 @@ -package config - -import ( - "time" - - "github.com/spf13/pflag" -) - -type MetricsConfig struct { - BindAddress string `json:"bind_address"` - EnableHTTPS bool `json:"enable_https"` - LabelMetricsInclusionDuration time.Duration `json:"label_metrics_inclusion_duration"` -} - -func NewMetricsConfig() *MetricsConfig { - return &MetricsConfig{ - BindAddress: "localhost:8080", - EnableHTTPS: false, - LabelMetricsInclusionDuration: 7 * 24 * time.Hour, - } -} - -func (s *MetricsConfig) AddFlags(fs *pflag.FlagSet) { - fs.StringVar(&s.BindAddress, "metrics-server-bindaddress", s.BindAddress, "Metrics server bind adddress") - fs.BoolVar(&s.EnableHTTPS, "enable-metrics-https", s.EnableHTTPS, "Enable HTTPS for metrics server") - fs.DurationVar(&s.LabelMetricsInclusionDuration, "label-metrics-inclusion-duration", 7*24*time.Hour, "A cluster's last telemetry date needs be within in this duration in order to have labels collected") -} - -func (s *MetricsConfig) ReadFiles() error { - return nil -} diff --git a/pkg/config/ocm.go b/pkg/config/ocm.go deleted file mode 100755 index 1f7c6ab..0000000 --- a/pkg/config/ocm.go +++ /dev/null @@ -1,56 +0,0 @@ -package config - -import ( - "github.com/spf13/pflag" -) - -type OCMConfig struct { - BaseURL string `json:"base_url"` - ClientID string `json:"client-id"` - ClientIDFile string `json:"client-id_file"` - ClientSecret string `json:"client-secret"` - ClientSecretFile string `json:"client-secret_file"` - SelfToken string `json:"self_token"` - SelfTokenFile string `json:"self_token_file"` - TokenURL string `json:"token_url"` - Debug bool `json:"debug"` - EnableMock bool `json:"enable_mock"` -} - -func NewOCMConfig() *OCMConfig { - return &OCMConfig{ - BaseURL: "https://api.integration.openshift.com", - TokenURL: "https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token", - ClientIDFile: "secrets/ocm-service.clientId", - ClientSecretFile: "secrets/ocm-service.clientSecret", - SelfTokenFile: "", - Debug: false, - EnableMock: true, - } -} - -func (c *OCMConfig) AddFlags(fs *pflag.FlagSet) { - fs.StringVar(&c.ClientIDFile, "ocm-client-id-file", c.ClientIDFile, "File containing OCM API privileged account client-id") - fs.StringVar(&c.ClientSecretFile, "ocm-client-secret-file", c.ClientSecretFile, "File containing OCM API privileged account client-secret") - fs.StringVar(&c.SelfTokenFile, "self-token-file", c.SelfTokenFile, "File containing OCM API privileged offline SSO token") - fs.StringVar(&c.BaseURL, "ocm-base-url", c.BaseURL, "The base URL of the OCM API, integration by default") - fs.StringVar(&c.TokenURL, "ocm-token-url", c.TokenURL, "The base URL that OCM uses to request tokens, stage by default") - fs.BoolVar(&c.Debug, "ocm-debug", c.Debug, "Debug flag for OCM API") - fs.BoolVar(&c.EnableMock, "enable-ocm-mock", c.EnableMock, "Enable mock ocm clients") -} - -func (c *OCMConfig) ReadFiles() error { - if c.EnableMock { - return nil - } - err := readFileValueString(c.ClientIDFile, &c.ClientID) - if err != nil { - return err - } - err = readFileValueString(c.ClientSecretFile, &c.ClientSecret) - if err != nil { - return err - } - err = readFileValueString(c.SelfTokenFile, &c.SelfToken) - return err -} diff --git a/pkg/config/sentry.go b/pkg/config/sentry.go deleted file mode 100755 index 1f3ecd2..0000000 --- a/pkg/config/sentry.go +++ /dev/null @@ -1,45 +0,0 @@ -package config - -import ( - "time" - - "github.com/spf13/pflag" -) - -type SentryConfig struct { - Enabled bool `json:"enabled"` - Key string `json:"key"` - URL string `json:"url"` - Project string `json:"project"` - Debug bool `json:"debug"` - Timeout time.Duration `json:"timeout"` - - KeyFile string `json:"key_file"` -} - -func NewSentryConfig() *SentryConfig { - return &SentryConfig{ - Enabled: false, - Key: "", - URL: "glitchtip.devshift.net", - Project: "53", // 16 is the ocm-service-dev project for local dev/testing - Debug: false, - KeyFile: "secrets/sentry.key", - } -} - -func (c *SentryConfig) AddFlags(fs *pflag.FlagSet) { - fs.BoolVar(&c.Enabled, "enable-sentry", c.Enabled, "Enable sentry error monitoring") - fs.StringVar(&c.KeyFile, "sentry-key-file", c.KeyFile, "File containing Sentry key") - fs.StringVar(&c.URL, "sentry-url", c.URL, "Base URL of Sentry isntance") - fs.StringVar(&c.Project, "sentry-project", c.Project, "Sentry project to report to") - fs.BoolVar(&c.Debug, "enable-sentry-debug", c.Debug, "Enable sentry error monitoring") - fs.DurationVar(&c.Timeout, "sentry-timeout", 5*time.Second, "Timeout for all requests made to Sentry") -} - -func (c *SentryConfig) ReadFiles() error { - if !c.Enabled { - return nil - } - return readFileValueString(c.KeyFile, &c.Key) -} diff --git a/pkg/config/server.go b/pkg/config/server.go deleted file mode 100755 index 9258fd8..0000000 --- a/pkg/config/server.go +++ /dev/null @@ -1,58 +0,0 @@ -package config - -import ( - "time" - - "github.com/spf13/pflag" -) - -type ServerConfig struct { - Hostname string `json:"hostname"` - BindAddress string `json:"bind_address"` - ReadTimeout time.Duration `json:"read_timeout"` - WriteTimeout time.Duration `json:"write_timeout"` - HTTPSCertFile string `json:"https_cert_file"` - HTTPSKeyFile string `json:"https_key_file"` - EnableHTTPS bool `json:"enable_https"` - EnableJWT bool `json:"enable_jwt"` - EnableAuthz bool `json:"enable_authz"` - JwkCertFile string `json:"jwk_cert_file"` - JwkCertURL string `json:"jwk_cert_url"` - ACLFile string `json:"acl_file"` -} - -func NewServerConfig() *ServerConfig { - return &ServerConfig{ - Hostname: "", - BindAddress: "localhost:8000", - ReadTimeout: 5 * time.Second, - WriteTimeout: 30 * time.Second, - EnableHTTPS: false, - EnableJWT: true, - EnableAuthz: true, - JwkCertFile: "", - JwkCertURL: "https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/certs", - ACLFile: "", - HTTPSCertFile: "", - HTTPSKeyFile: "", - } -} - -func (s *ServerConfig) AddFlags(fs *pflag.FlagSet) { - fs.StringVar(&s.BindAddress, "api-server-bindaddress", s.BindAddress, "API server bind adddress") - fs.StringVar(&s.Hostname, "api-server-hostname", s.Hostname, "Server's public hostname") - fs.DurationVar(&s.ReadTimeout, "http-read-timeout", s.ReadTimeout, "HTTP server read timeout") - fs.DurationVar(&s.WriteTimeout, "http-write-timeout", s.WriteTimeout, "HTTP server write timeout") - fs.StringVar(&s.HTTPSCertFile, "https-cert-file", s.HTTPSCertFile, "The path to the tls.crt file.") - fs.StringVar(&s.HTTPSKeyFile, "https-key-file", s.HTTPSKeyFile, "The path to the tls.key file.") - fs.BoolVar(&s.EnableHTTPS, "enable-https", s.EnableHTTPS, "Enable HTTPS rather than HTTP") - fs.BoolVar(&s.EnableJWT, "enable-jwt", s.EnableJWT, "Enable JWT authentication validation") - fs.BoolVar(&s.EnableAuthz, "enable-authz", s.EnableAuthz, "Enable Authorization on endpoints, should only be disabled for debug") - fs.StringVar(&s.JwkCertFile, "jwk-cert-file", s.JwkCertFile, "JWK Certificate file") - fs.StringVar(&s.JwkCertURL, "jwk-cert-url", s.JwkCertURL, "JWK Certificate URL") - fs.StringVar(&s.ACLFile, "acl-file", s.ACLFile, "Access control list file") -} - -func (s *ServerConfig) ReadFiles() error { - return nil -} diff --git a/pkg/controllers/framework.go b/pkg/controllers/framework.go deleted file mode 100755 index 5a50a7d..0000000 --- a/pkg/controllers/framework.go +++ /dev/null @@ -1,138 +0,0 @@ -package controllers - -import ( - "context" - "fmt" - "time" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" -) - -/* -This controller pattern mimics upstream Kube-style controllers with Add/Update/Delete events with periodic -sync-the-world for any messages missed. - -The implementation is specific to the Event table in this service and leverages features of PostresDB: - - 1. pg_notify(channel, msg) is used for real time notification to listeners - 2. advisory locks are used for concurrency when doing background work - -DAOs decorated similarly to the DinosaurDAO will persist Events to the database and listeners are notified of the changed. -A worker attemping to process the Event will first obtain a fail-fast adivosry lock. Of many competing workers, only -one would first successfully obtain the lock. All other workers will *not* wait to obtain the lock. - -Any successful processing of an Event will remove it from the Events table permanently. - -A periodic process reads from the Events table and calls pg_notify, ensuring any failed Events are re-processed. Competing -consumers for the lock will fail fast on redundant messages. - -*/ - -type ControllerHandlerFunc func(ctx context.Context, id string) error - -type ControllerConfig struct { - Source string - Handlers map[api.EventType][]ControllerHandlerFunc -} - -type KindControllerManager struct { - controllers map[string]map[api.EventType][]ControllerHandlerFunc - lockFactory db.LockFactory - events services.EventService -} - -func NewKindControllerManager(lockFactory db.LockFactory, events services.EventService) *KindControllerManager { - return &KindControllerManager{ - controllers: map[string]map[api.EventType][]ControllerHandlerFunc{}, - lockFactory: lockFactory, - events: events, - } -} - -func (km *KindControllerManager) Add(config *ControllerConfig) { - for ev, fn := range config.Handlers { - km.add(config.Source, ev, fn) - } -} - -func (km *KindControllerManager) add(source string, ev api.EventType, fns []ControllerHandlerFunc) { - if _, exists := km.controllers[source]; !exists { - km.controllers[source] = map[api.EventType][]ControllerHandlerFunc{} - } - - if _, exists := km.controllers[source][ev]; !exists { - km.controllers[source][ev] = []ControllerHandlerFunc{} - } - - for _, fn := range fns { - km.controllers[source][ev] = append(km.controllers[source][ev], fn) - } -} - -func (km *KindControllerManager) Handle(id string) { - - ctx := context.Background() - logger := logger.NewOCMLogger(ctx) - - // lock the Event with a fail-fast advisory lock context. - // this allows concurrent processing of many events by one or many controller managers. - // allow the lock to be released by the handler goroutine and allow this function to continue. - // subsequent events will be locked by their own distinct IDs. - lockOwnerID, acquired, err := km.lockFactory.NewNonBlockingLock(ctx, id, db.Events) - defer km.lockFactory.Unlock(ctx, lockOwnerID) - if err != nil { - logger.Error(fmt.Sprintf("Error obtaining the event lock: %v", err)) - return - } - if !acquired { - logger.Infof("Event %s is processed by another worker, continue to process the next", id) - return - } - threadContext := context.WithValue(ctx, "event", id) - - km.handle(threadContext, id) -} - -func (km *KindControllerManager) handle(ctx context.Context, id string) { - - log := logger.NewOCMLogger(ctx) - - event, err := km.events.Get(ctx, id) - - if err != nil { - log.Error(err.Error()) - return - } - - source, found := km.controllers[event.Source] - if !found { - log.Infof("No controllers found for '%s'\n", event.Source) - return - } - - handlerFns, found := source[event.EventType] - if !found { - log.Infof("No handler functions found for '%s-%s'\n", event.Source, event.EventType) - return - } - - for _, fn := range handlerFns { - err := fn(ctx, event.SourceID) - if err != nil { - errStr := fmt.Sprintf("error handing event %s, %s, %s: %s", event.Source, event.EventType, id, err) - log.Error(errStr) - return - } - } - - // all handlers successfully executed - now := time.Now() - event.ReconciledDate = &now - _, err = km.events.Replace(ctx, event) - if err != nil { - log.Error(err.Error()) - } -} diff --git a/pkg/controllers/framework_test.go b/pkg/controllers/framework_test.go deleted file mode 100755 index ccd082b..0000000 --- a/pkg/controllers/framework_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package controllers - -import ( - "context" - "testing" - - . "github.com/onsi/gomega" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao/mocks" - dbmocks "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/mocks" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" -) - -func newExampleControllerConfig(ctrl *exampleController) *ControllerConfig { - return &ControllerConfig{ - Source: "my-event-source", - Handlers: map[api.EventType][]ControllerHandlerFunc{ - api.CreateEventType: {ctrl.OnAdd}, - api.UpdateEventType: {ctrl.OnUpdate}, - api.DeleteEventType: {ctrl.OnDelete}, - }, - } -} - -type exampleController struct { - addCounter int - updateCounter int - deleteCounter int -} - -func (d *exampleController) OnAdd(ctx context.Context, id string) error { - d.addCounter++ - return nil -} - -func (d *exampleController) OnUpdate(ctx context.Context, id string) error { - d.updateCounter++ - return nil -} - -func (d *exampleController) OnDelete(ctx context.Context, id string) error { - d.deleteCounter++ - return nil -} - -func TestControllerFramework(t *testing.T) { - RegisterTestingT(t) - - ctx := context.Background() - eventsDao := mocks.NewEventDao() - events := services.NewEventService(eventsDao) - mgr := NewKindControllerManager(dbmocks.NewMockAdvisoryLockFactory(), events) - - ctrl := &exampleController{} - config := newExampleControllerConfig(ctrl) - mgr.Add(config) - - _, _ = eventsDao.Create(ctx, &api.Event{ - Meta: api.Meta{ID: "1"}, - Source: config.Source, - SourceID: "any id", - EventType: api.CreateEventType, - }) - - _, _ = eventsDao.Create(ctx, &api.Event{ - Meta: api.Meta{ID: "2"}, - Source: config.Source, - SourceID: "any id", - EventType: api.UpdateEventType, - }) - - _, _ = eventsDao.Create(ctx, &api.Event{ - Meta: api.Meta{ID: "3"}, - Source: config.Source, - SourceID: "any id", - EventType: api.DeleteEventType, - }) - - mgr.Handle("1") - mgr.Handle("2") - mgr.Handle("3") - - Expect(ctrl.addCounter).To(Equal(1)) - Expect(ctrl.updateCounter).To(Equal(1)) - Expect(ctrl.deleteCounter).To(Equal(1)) - - eve, _ := eventsDao.Get(ctx, "1") - Expect(eve.ReconciledDate).ToNot(BeNil(), "event reconcile date should be set") -} diff --git a/pkg/dao/dinosaur.go b/pkg/dao/dinosaur.go deleted file mode 100755 index 6208a0c..0000000 --- a/pkg/dao/dinosaur.go +++ /dev/null @@ -1,93 +0,0 @@ -package dao - -import ( - "context" - - "gorm.io/gorm/clause" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" -) - -type DinosaurDao interface { - Get(ctx context.Context, id string) (*api.Dinosaur, error) - Create(ctx context.Context, dinosaur *api.Dinosaur) (*api.Dinosaur, error) - Replace(ctx context.Context, dinosaur *api.Dinosaur) (*api.Dinosaur, error) - Delete(ctx context.Context, id string) error - FindByIDs(ctx context.Context, ids []string) (api.DinosaurList, error) - FindBySpecies(ctx context.Context, species string) (api.DinosaurList, error) - All(ctx context.Context) (api.DinosaurList, error) -} - -var _ DinosaurDao = &sqlDinosaurDao{} - -type sqlDinosaurDao struct { - sessionFactory *db.SessionFactory -} - -func NewDinosaurDao(sessionFactory *db.SessionFactory) DinosaurDao { - return &sqlDinosaurDao{sessionFactory: sessionFactory} -} - -func (d *sqlDinosaurDao) Get(ctx context.Context, id string) (*api.Dinosaur, error) { - g2 := (*d.sessionFactory).New(ctx) - var dinosaur api.Dinosaur - if err := g2.Take(&dinosaur, "id = ?", id).Error; err != nil { - return nil, err - } - return &dinosaur, nil -} - -func (d *sqlDinosaurDao) Create(ctx context.Context, dinosaur *api.Dinosaur) (*api.Dinosaur, error) { - g2 := (*d.sessionFactory).New(ctx) - if err := g2.Omit(clause.Associations).Create(dinosaur).Error; err != nil { - db.MarkForRollback(ctx, err) - return nil, err - } - return dinosaur, nil -} - -func (d *sqlDinosaurDao) Replace(ctx context.Context, dinosaur *api.Dinosaur) (*api.Dinosaur, error) { - g2 := (*d.sessionFactory).New(ctx) - if err := g2.Omit(clause.Associations).Save(dinosaur).Error; err != nil { - db.MarkForRollback(ctx, err) - return nil, err - } - return dinosaur, nil -} - -func (d *sqlDinosaurDao) Delete(ctx context.Context, id string) error { - g2 := (*d.sessionFactory).New(ctx) - if err := g2.Omit(clause.Associations).Delete(&api.Dinosaur{Meta: api.Meta{ID: id}}).Error; err != nil { - db.MarkForRollback(ctx, err) - return err - } - return nil -} - -func (d *sqlDinosaurDao) FindByIDs(ctx context.Context, ids []string) (api.DinosaurList, error) { - g2 := (*d.sessionFactory).New(ctx) - dinosaurs := api.DinosaurList{} - if err := g2.Where("id in (?)", ids).Find(&dinosaurs).Error; err != nil { - return nil, err - } - return dinosaurs, nil -} - -func (d *sqlDinosaurDao) FindBySpecies(ctx context.Context, species string) (api.DinosaurList, error) { - g2 := (*d.sessionFactory).New(ctx) - dinosaurs := api.DinosaurList{} - if err := g2.Where("species = ?", species).Find(&dinosaurs).Error; err != nil { - return nil, err - } - return dinosaurs, nil -} - -func (d *sqlDinosaurDao) All(ctx context.Context) (api.DinosaurList, error) { - g2 := (*d.sessionFactory).New(ctx) - dinosaurs := api.DinosaurList{} - if err := g2.Find(&dinosaurs).Error; err != nil { - return nil, err - } - return dinosaurs, nil -} diff --git a/pkg/dao/event.go b/pkg/dao/event.go deleted file mode 100755 index 1f08272..0000000 --- a/pkg/dao/event.go +++ /dev/null @@ -1,92 +0,0 @@ -package dao - -import ( - "context" - "fmt" - - "gorm.io/gorm/clause" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" -) - -type EventDao interface { - Get(ctx context.Context, id string) (*api.Event, error) - Create(ctx context.Context, event *api.Event) (*api.Event, error) - Replace(ctx context.Context, event *api.Event) (*api.Event, error) - Delete(ctx context.Context, id string) error - FindByIDs(ctx context.Context, ids []string) (api.EventList, error) - All(ctx context.Context) (api.EventList, error) -} - -var _ EventDao = &sqlEventDao{} - -type sqlEventDao struct { - sessionFactory *db.SessionFactory -} - -func NewEventDao(sessionFactory *db.SessionFactory) EventDao { - return &sqlEventDao{sessionFactory: sessionFactory} -} - -func (d *sqlEventDao) Get(ctx context.Context, id string) (*api.Event, error) { - g2 := (*d.sessionFactory).New(ctx) - var event api.Event - if err := g2.Take(&event, "id = ?", id).Error; err != nil { - return nil, err - } - return &event, nil -} - -func (d *sqlEventDao) Create(ctx context.Context, event *api.Event) (*api.Event, error) { - g2 := (*d.sessionFactory).New(ctx) - if err := g2.Omit(clause.Associations).Create(event).Error; err != nil { - db.MarkForRollback(ctx, err) - return nil, err - } - - notify := fmt.Sprintf("select pg_notify('%s', '%s')", "events", event.ID) - - err := g2.Exec(notify).Error - if err != nil { - return nil, err - } - - return event, nil -} - -func (d *sqlEventDao) Replace(ctx context.Context, event *api.Event) (*api.Event, error) { - g2 := (*d.sessionFactory).New(ctx) - if err := g2.Omit(clause.Associations).Save(event).Error; err != nil { - db.MarkForRollback(ctx, err) - return nil, err - } - return event, nil -} - -func (d *sqlEventDao) Delete(ctx context.Context, id string) error { - g2 := (*d.sessionFactory).New(ctx) - if err := g2.Unscoped().Omit(clause.Associations).Delete(&api.Event{Meta: api.Meta{ID: id}}).Error; err != nil { - db.MarkForRollback(ctx, err) - return err - } - return nil -} - -func (d *sqlEventDao) FindByIDs(ctx context.Context, ids []string) (api.EventList, error) { - g2 := (*d.sessionFactory).New(ctx) - events := api.EventList{} - if err := g2.Where("id in (?)", ids).Find(&events).Error; err != nil { - return nil, err - } - return events, nil -} - -func (d *sqlEventDao) All(ctx context.Context) (api.EventList, error) { - g2 := (*d.sessionFactory).New(ctx) - events := api.EventList{} - if err := g2.Find(&events).Error; err != nil { - return nil, err - } - return events, nil -} diff --git a/pkg/dao/generic.go b/pkg/dao/generic.go deleted file mode 100755 index 569c486..0000000 --- a/pkg/dao/generic.go +++ /dev/null @@ -1,152 +0,0 @@ -package dao - -import ( - "context" - "strings" - - "github.com/jinzhu/inflection" - "gorm.io/gorm" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" -) - -type Where struct { - sql string - values []any -} - -func NewWhere(sql string, values []any) Where { - return Where{ - sql: sql, - values: values, - } -} - -type GenericDao interface { - Fetch(offset int, limit int, resourceList interface{}) error - - GetInstanceDao(ctx context.Context, model interface{}) GenericDao - Preload(preload string) - OrderBy(orderBy string) - Joins(sql string) - Group(sql string) - Where(where Where) - Count(model interface{}, total *int64) - Validate(resourceList interface{}) error - - GetTableName() string - GetTableRelation(fieldName string) (TableRelation, bool) -} - -var _ GenericDao = &sqlGenericDao{} - -type sqlGenericDao struct { - sessionFactory *db.SessionFactory - g2 *gorm.DB -} - -// TableRelation represents a relationship between two tables. They can be joined, -// ON TableName.ColumnName = ForeignTableName.ForeignColumnName -type TableRelation struct { - TableName string - ColumnName string - ForeignTableName string - ForeignColumnName string -} - -func NewGenericDao(sessionFactory *db.SessionFactory) GenericDao { - return &sqlGenericDao{sessionFactory: sessionFactory} -} - -func (d *sqlGenericDao) GetInstanceDao(ctx context.Context, model interface{}) GenericDao { - return &sqlGenericDao{ - sessionFactory: d.sessionFactory, - g2: (*d.sessionFactory).New(ctx).Model(model), - } -} - -func (d *sqlGenericDao) Fetch(offset int, limit int, resourceList interface{}) error { - return d.g2.Debug().Offset(offset).Limit(limit).Find(resourceList).Error -} - -func (d *sqlGenericDao) Preload(preload string) { - d.g2 = d.g2.Preload(preload) -} - -func (d *sqlGenericDao) OrderBy(orderBy string) { - d.g2 = d.g2.Order(orderBy) -} - -func (d *sqlGenericDao) Joins(sql string) { - d.g2 = d.g2.Joins(sql) -} - -func (d *sqlGenericDao) Group(sql string) { - d.g2 = d.g2.Group(sql) -} - -func (d *sqlGenericDao) Where(where Where) { - d.g2 = d.g2.Where(where.sql, where.values...) -} - -func (d *sqlGenericDao) Count(model interface{}, total *int64) { - // Creates new session which already clears all statement clauses - g2 := d.g2.Session(&gorm.Session{DryRun: false}).Model(model) - // Considers existing joins and search params from previous session - if len(d.g2.Statement.Joins) > 0 { - g2.Statement.Joins = d.g2.Statement.Joins - } - if where, ok := d.g2.Statement.Clauses["WHERE"]; ok { - g2.Statement.Clauses["WHERE"] = where - } - g2.Count(total) -} - -// Gorm finishers (Take, First, Last, etc.) are not idempotent -// Use a new session to execute these checks -func (d *sqlGenericDao) Validate(resourceList interface{}) error { - if err := d.g2.Session(&gorm.Session{DryRun: false}).Take(resourceList).Error; err != nil { - return err - } - return nil -} - -func (d *sqlGenericDao) GetTableName() string { - return db.GetTableName(d.g2) -} - -// extract the relation from the api model -func (d *sqlGenericDao) GetTableRelation(fieldName string) (TableRelation, bool) { - // try singular - fieldName = strings.ToUpper(fieldName[:1]) + fieldName[1:] - table := inflection.Singular(fieldName) - association := d.g2.Association(table) - // the relation must exist in the model - if association.Relationship == nil { - // try plural - table = inflection.Plural(fieldName) - association = d.g2.Association(table) - if association.Relationship == nil { - return TableRelation{}, false - } - } - - if association.Relationship.Type != "belongs_to" && association.Relationship.Type != "has_many" { - // we don't use has_one or many_to_many relations - return TableRelation{}, false - } - - columnName := association.Relationship.References[0].ForeignKey.DBName - foreignColumnName := association.Relationship.References[0].PrimaryKey.DBName - if association.Relationship.Type == "has_many" { - columnName = association.Relationship.References[0].PrimaryKey.DBName - foreignColumnName = association.Relationship.References[0].ForeignKey.DBName - } - - return TableRelation{ - TableName: association.Relationship.Field.Schema.Table, - ForeignTableName: association.Relationship.FieldSchema.Table, - ForeignColumnName: foreignColumnName, - ColumnName: columnName, - }, true -} diff --git a/pkg/dao/mocks/dinosaur.go b/pkg/dao/mocks/dinosaur.go deleted file mode 100755 index 55221dc..0000000 --- a/pkg/dao/mocks/dinosaur.go +++ /dev/null @@ -1,62 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - - "gorm.io/gorm" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -var _ dao.DinosaurDao = &dinosaurDaoMock{} - -type dinosaurDaoMock struct { - dinosaurs api.DinosaurList -} - -func NewDinosaurDao() *dinosaurDaoMock { - return &dinosaurDaoMock{} -} - -func (d *dinosaurDaoMock) Get(ctx context.Context, id string) (*api.Dinosaur, error) { - for _, dino := range d.dinosaurs { - if dino.ID == id { - return dino, nil - } - } - return nil, gorm.ErrRecordNotFound -} - -func (d *dinosaurDaoMock) Create(ctx context.Context, dinosaur *api.Dinosaur) (*api.Dinosaur, error) { - d.dinosaurs = append(d.dinosaurs, dinosaur) - return dinosaur, nil -} - -func (d *dinosaurDaoMock) Replace(ctx context.Context, dinosaur *api.Dinosaur) (*api.Dinosaur, error) { - return nil, errors.NotImplemented("Dinosaur").AsError() -} - -func (d *dinosaurDaoMock) Delete(ctx context.Context, id string) error { - return errors.NotImplemented("Dinosaur").AsError() -} - -func (d *dinosaurDaoMock) FindByIDs(ctx context.Context, ids []string) (api.DinosaurList, error) { - return nil, errors.NotImplemented("Dinosaur").AsError() -} - -func (d *dinosaurDaoMock) FindBySpecies(ctx context.Context, species string) (api.DinosaurList, error) { - var dinos api.DinosaurList - for _, dino := range d.dinosaurs { - if dino.Species == species { - dinos = append(dinos, dino) - } - } - return dinos, nil -} - -func (d *dinosaurDaoMock) All(ctx context.Context) (api.DinosaurList, error) { - return d.dinosaurs, nil -} diff --git a/pkg/dao/mocks/event.go b/pkg/dao/mocks/event.go deleted file mode 100755 index 58fc180..0000000 --- a/pkg/dao/mocks/event.go +++ /dev/null @@ -1,61 +0,0 @@ -package mocks - -import ( - "context" - - "gorm.io/gorm" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -var _ dao.EventDao = &eventDaoMock{} - -type eventDaoMock struct { - events api.EventList -} - -func NewEventDao() *eventDaoMock { - return &eventDaoMock{} -} - -func (d *eventDaoMock) Get(ctx context.Context, id string) (*api.Event, error) { - for _, dino := range d.events { - if dino.ID == id { - return dino, nil - } - } - return nil, gorm.ErrRecordNotFound -} - -func (d *eventDaoMock) Create(ctx context.Context, event *api.Event) (*api.Event, error) { - d.events = append(d.events, event) - return event, nil -} - -func (d *eventDaoMock) Replace(ctx context.Context, event *api.Event) (*api.Event, error) { - return nil, errors.NotImplemented("Event").AsError() -} - -func (d *eventDaoMock) Delete(ctx context.Context, id string) error { - newEvents := api.EventList{} - for _, e := range d.events { - if e.ID == id { - // deleting this one - // do not include in the new list - } else { - newEvents = append(newEvents, e) - } - } - d.events = newEvents - return nil -} - -func (d *eventDaoMock) FindByIDs(ctx context.Context, ids []string) (api.EventList, error) { - return nil, errors.NotImplemented("Event").AsError() -} - -func (d *eventDaoMock) All(ctx context.Context) (api.EventList, error) { - return d.events, nil -} diff --git a/pkg/dao/mocks/generic.go b/pkg/dao/mocks/generic.go deleted file mode 100755 index 1e513af..0000000 --- a/pkg/dao/mocks/generic.go +++ /dev/null @@ -1,76 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" -) - -var _ dao.GenericDao = &genericDaoMock{} - -type genericDaoMock struct { - preload string - orderBy string - joins string - group string - wheres []dao.Where - model interface{} -} - -func NewGenericDao() *genericDaoMock { - return &genericDaoMock{ - wheres: []dao.Where{}, - } -} - -func (g *genericDaoMock) Fetch(offset int, limit int, resourceList interface{}) error { - // Mock implementation - does nothing but returns no error - return nil -} - -func (g *genericDaoMock) GetInstanceDao(ctx context.Context, model interface{}) dao.GenericDao { - return &genericDaoMock{ - model: model, - wheres: []dao.Where{}, - } -} - -func (g *genericDaoMock) Preload(preload string) { - g.preload = preload -} - -func (g *genericDaoMock) OrderBy(orderBy string) { - g.orderBy = orderBy -} - -func (g *genericDaoMock) Joins(sql string) { - g.joins = sql -} - -func (g *genericDaoMock) Group(sql string) { - g.group = sql -} - -func (g *genericDaoMock) Where(where dao.Where) { - g.wheres = append(g.wheres, where) -} - -func (g *genericDaoMock) Count(model interface{}, total *int64) { - // Mock implementation - sets count to 0 - *total = 0 -} - -func (g *genericDaoMock) Validate(resourceList interface{}) error { - // Mock implementation - returns no error - return nil -} - -func (g *genericDaoMock) GetTableName() string { - // Mock implementation - returns empty string - return "" -} - -func (g *genericDaoMock) GetTableRelation(fieldName string) (dao.TableRelation, bool) { - // Mock implementation - returns empty relation and false - return dao.TableRelation{}, false -} diff --git a/pkg/db/README.md b/pkg/db/README.md deleted file mode 100755 index 763802f..0000000 --- a/pkg/db/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# Migrations - -Database migrations are handled by this package. All migrations should be created in separate files, following a starndard naming convetion - -The `migrations.go` file defines an array of migrate functions that are called by the [gormigrate](https://gopkg.in/gormigrate.v1) helper. Each migration function should perform a specific migration. - -## Creating a new migration - -Create a migration ID based on the time using the YYYYMMDDHHMM format. Example: `August 21 2018 at 2:54pm` would be `201808211454`. - -Your migration's name should be used in the file name and in the function name and should adequately represent the actions your migration is taking. If your migration is doing too much to fit in a name, you should consider creating multiple migrations. - -Create a separate file in `pkg/db/` following the naming schema in place: `_.go`. In the file, you'll create a function that returns a [gormmigrate.Migration](https://gopkg.in/gormigrate.v1/blob/master/gormigrate.go#L37) object with `gormigrate.Migrate` and `gormigrate.Rollback` functions defined. - -Add the function you created in the separate file to the `migrations` list in `pkg/db/migrations.go`. - -If necessary, write a test to verify the migration. See `test/integration/migrations_test.go` for examples. - -## Migration Rules - -### Migration IDs - -Each migration has an ID that defines the order in which the migration is run. - -IDs are numerical timestamps that must sort ascending. Use YYYYMMDDHHMM w/ 24 hour time for format. -Example: `August 21 2018 at 2:54pm` would be `201808211454`. - -Migration IDs must be descending. If you create a migration, submit an MR, and another MR is merged before yours is able to be merged, you must update the ID to represent a date later than any previous migration. - -### Models in Migrations - -Represent modesl inline with migrations to represent the evolution of the object over time. - -For example, it is necessary to add a boolean field "hidden" to the "Account" model. This is how you would represent the account model in that migration: -```golang -type Account struct { - Model - Username string - FirstName string - LastName string - Hidden boolean -} - -err := tx.AutoMigrate(&Account{}).Error -if err != nil { -... -``` - -**DO NOT IMPORT THE API PKG**. When a migration imports the `api` pkg and uses models defined in it, the migration may work the first time it is run. The models in `pkg/api` are bound to change as the project grows. Eventually, the models could change so that the migration breaks, causing any new deployments to fail on your old shitty migration. - -### Record Deletions - -If it is necessary to delete a record in a migration, be aware of a couple caveats around deleting wth gorm: - -1. You must pass a record with an ID to `gorm.Delete(&record)`, otherwise **ALL RECORDS WILL BE DELETED** - -2. Gorm [soft deletes](http://gorm.io/docs/delete.html#Soft-Delete) by default. This means it sets the `deleted_at` field to a non-null value and any subsequent gorm calls will ignore it. If you are deleting a record that needs to be permanently deleted (like permissions), use `gorm.Unscoped().Delete`. - -See the [gorm documentation around deletions](http://gorm.io/docs/delete.html) for more information - -## Migration tests - -In most cases, it shouldn't be necessary to create a test for a migration. However, if the migration is manipulating records and poses a significant risk of completely borking up important data, a test should be written. - -Tests are difficult to write for migrations and are likely to fail one day long after the migration has already run in production. After a migration is run in production, it is safe to delete the test from the integration test suite. - -The test `helper` has a couple helpful functions for testing migrations. You can use `h.CleanDB()` to completely wipe the database clean, then `h.MigrateTo()` to migrate to a specific migration ID. You should then be able to create whatever records in the database you need to test against and finally run `h.MigrateDB()` to run your created migration and all subsequent migrations. diff --git a/pkg/db/advisory_locks.go b/pkg/db/advisory_locks.go deleted file mode 100755 index 010ec4d..0000000 --- a/pkg/db/advisory_locks.go +++ /dev/null @@ -1,273 +0,0 @@ -package db - -import ( - "context" - "errors" - "fmt" - "hash/fnv" - "sync" - "time" - - "github.com/google/uuid" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" - "gorm.io/gorm" -) - -type ( - advisoryLockMap map[string]*AdvisoryLock - LockType string -) - -const ( - Migrations LockType = "migrations" - Dinosaurs LockType = "dinosaurs" - Events LockType = "events" -) - -// LockFactory provides the blocking/unblocking locks based on PostgreSQL advisory lock. -type LockFactory interface { - // NewAdvisoryLock constructs a new AdvisoryLock that is a blocking PostgreSQL advisory lock - // defined by (id, lockType) and returns a UUID as this AdvisoryLock owner id. - NewAdvisoryLock(ctx context.Context, id string, lockType LockType) (string, error) - // NewNonBlockingLock constructs a new nonblocking AdvisoryLock defined by (id, lockType), - // returns a UUID and a boolean on whether the lock is acquired. - NewNonBlockingLock(ctx context.Context, id string, lockType LockType) (string, bool, error) - // Unlock unlocks one AdvisoryLock by its owner id. - Unlock(ctx context.Context, uuid string) -} - -type AdvisoryLockFactory struct { - connection SessionFactory - locks advisoryLockMap - mutex sync.RWMutex -} - -// NewAdvisoryLockFactory returns a new factory with AdvisoryLock stored in it. -func NewAdvisoryLockFactory(connection SessionFactory) *AdvisoryLockFactory { - return &AdvisoryLockFactory{ - connection: connection, - locks: make(advisoryLockMap), - } -} - -func (f *AdvisoryLockFactory) NewAdvisoryLock(ctx context.Context, id string, lockType LockType) (string, error) { - log := logger.NewOCMLogger(ctx) - - lock, err := f.newLock(ctx, id, lockType) - if err != nil { - return "", err - } - - // obtain the advisory lock (blocking) - if err := lock.lock(); err != nil { - UpdateAdvisoryLockCountMetric(lockType, "lock error") - errMsg := fmt.Sprintf("error obtaining the advisory lock for id %s type %s, %v", id, lockType, err) - log.Error(errMsg) - // the lock transaction is already started, if error happens, we return the transaction id, so that the caller - // can end this transaction. - return *lock.uuid, fmt.Errorf("%s", errMsg) - } - - log.V(4).Info(fmt.Sprintf("Locked advisory lock id=%s type=%s - owner=%s", id, lockType, *lock.uuid)) - f.mutex.Lock() - f.locks[*lock.uuid] = lock - f.mutex.Unlock() - return *lock.uuid, nil -} - -func (f *AdvisoryLockFactory) NewNonBlockingLock(ctx context.Context, id string, lockType LockType) (string, bool, error) { - log := logger.NewOCMLogger(ctx) - - lock, err := f.newLock(ctx, id, lockType) - if err != nil { - return "", false, err - } - - // obtain the advisory lock (unblocking) - acquired, err := lock.nonBlockingLock() - if err != nil { - UpdateAdvisoryLockCountMetric(lockType, "lock error") - errMsg := fmt.Sprintf("error obtaining the non blocking advisory lock for id %s type %s, %v", id, lockType, err) - log.Error(errMsg) - // the lock transaction is already started, if error happens, we return the transaction id, so that the caller - // can end this transaction. - return *lock.uuid, false, fmt.Errorf("%s", errMsg) - } - - log.V(4).Info(fmt.Sprintf("Locked non blocking advisory lock id=%s type=%s - owner=%s", id, lockType, *lock.uuid)) - f.mutex.Lock() - f.locks[*lock.uuid] = lock - f.mutex.Unlock() - return *lock.uuid, acquired, nil -} - -func (f *AdvisoryLockFactory) newLock(ctx context.Context, id string, lockType LockType) (*AdvisoryLock, error) { - // lockOwnerID will be different for every service function that attempts to start a lock. - // only the initial call in the stack must unlock. - // Unlock() will compare UUIDs and ensure only the top level call succeeds. - lockOwnerID := uuid.New().String() - lock, err := newAdvisoryLock(ctx, f.connection) - if err != nil { - return nil, err - } - - lock.uuid = &lockOwnerID - lock.id = &id - lock.lockType = &lockType - - return lock, nil -} - -// Unlock searches current locks and unlocks the one matching its owner id. -func (f *AdvisoryLockFactory) Unlock(ctx context.Context, uuid string) { - log := logger.NewOCMLogger(ctx) - - if uuid == "" { - return - } - - f.mutex.RLock() - lock, ok := f.locks[uuid] - f.mutex.RUnlock() - if !ok { - // the resolving UUID belongs to a service call that did *not* initiate the lock. - // we can safely ignore this, knowing the top-most func in the call stack - // will provide the correct UUID. - log.V(4).Info(fmt.Sprintf("Caller not lock owner. Owner %s", uuid)) - return - } - - lockType := *lock.lockType - lockID := "" - if lock.id != nil { - lockID = *lock.id - } - - if err := lock.unlock(); err != nil { - UpdateAdvisoryLockCountMetric(lockType, "unlock error") - log.Extra("lockID", lockID).Extra("owner", uuid).Error(fmt.Sprintf("Could not unlock, %v", err)) - } - - UpdateAdvisoryLockCountMetric(lockType, "OK") - UpdateAdvisoryLockDurationMetric(lockType, "OK", lock.startTime) - - log.V(4).Info(fmt.Sprintf("Unlocked lock id=%s type=%s - owner=%s", lockID, lockType, uuid)) - - f.mutex.Lock() - delete(f.locks, uuid) - f.mutex.Unlock() -} - -// AdvisoryLock represents a postgres advisory lock -// -// begin # start a Tx -// select pg_advisory_xact_lock(id, lockType) # obtain the lock (blocking) -// end # end the Tx and release the lock -// -// UUID is a way to own the lock. Only the very first -// service call that owns the lock will have the correct UUID. This is necessary -// to allow functions to call other service functions as part of the same lock (id, lockType). -type AdvisoryLock struct { - g2 *gorm.DB - txid int64 - uuid *string - id *string - lockType *LockType - startTime time.Time -} - -// newAdvisoryLock constructs a new AdvisoryLock object. -func newAdvisoryLock(ctx context.Context, connection SessionFactory) (*AdvisoryLock, error) { - // it requires a new DB session to start the advisory lock. - g2 := connection.New(ctx) - - // start a Tx to ensure gorm will obtain/release the lock using a same connection. - tx := g2.Begin() - if tx.Error != nil { - return nil, tx.Error - } - - // current transaction ID set by postgres. these are *not* distinct across time - // and do get reset after postgres performs "vacuuming" to reclaim used IDs. - var txid struct{ ID int64 } - tx.Raw("select txid_current() as id").Scan(&txid) - - return &AdvisoryLock{ - txid: txid.ID, - g2: tx, - startTime: time.Now(), - }, nil -} - -// lock calls select pg_advisory_xact_lock(id, lockType) to obtain the lock defined by (id, lockType). -// it is blocked if some other thread currently is holding the same lock (id, lockType). -// if blocked, it can be unblocked or timed out when overloaded. -func (l *AdvisoryLock) lock() error { - if l.g2 == nil { - return errors.New("AdvisoryLock: transaction is missing") - } - if l.id == nil { - return errors.New("AdvisoryLock: id is missing") - } - if l.lockType == nil { - return errors.New("AdvisoryLock: lockType is missing") - } - - idAsInt := hash(*l.id) - typeAsInt := hash(string(*l.lockType)) - err := l.g2.Exec("select pg_advisory_xact_lock(?, ?)", idAsInt, typeAsInt).Error - if err != nil { - return err - } - return nil -} - -func (l *AdvisoryLock) nonBlockingLock() (bool, error) { - if l.g2 == nil { - return false, errors.New("AdvisoryLock: transaction is missing") - } - if l.id == nil { - return false, errors.New("AdvisoryLock: id is missing") - } - if l.lockType == nil { - return false, errors.New("AdvisoryLock: lockType is missing") - } - - idAsInt := hash(*l.id) - typeAsInt := hash(string(*l.lockType)) - var acquired bool - var result string - err := l.g2.Raw("select pg_try_advisory_xact_lock(?, ?)", idAsInt, typeAsInt).Scan(&result).Error - if err != nil { - return false, err - } - if result == "true" { - acquired = true - } - - return acquired, nil -} - -func (l *AdvisoryLock) unlock() error { - if l.g2 == nil { - return errors.New("AdvisoryLock: transaction is missing") - } - - // it ends the Tx and implicitly releases the lock. - err := l.g2.Commit().Error - l.g2 = nil - l.uuid = nil - l.id = nil - l.lockType = nil - return err -} - -// hash string to int32 (postgres integer) -// https://pkg.go.dev/math#pkg-constants -// https://www.postgresql.org/docs/12/datatype-numeric.html -func hash(s string) int32 { - h := fnv.New32a() - h.Write([]byte(s)) - // Sum32() returns uint32. needs conversion. - return int32(h.Sum32()) -} diff --git a/pkg/db/context.go b/pkg/db/context.go deleted file mode 100755 index 60ce2eb..0000000 --- a/pkg/db/context.go +++ /dev/null @@ -1,57 +0,0 @@ -package db - -import ( - "context" - - dbContext "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/db_context" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" -) - -// NewContext returns a new context with transaction stored in it. -// Upon error, the original context is still returned along with an error -func NewContext(ctx context.Context, connection SessionFactory) (context.Context, error) { - tx, err := newTransaction(ctx, connection) - if err != nil { - return ctx, err - } - - ctx = dbContext.WithTransaction(ctx, tx) - - return ctx, nil -} - -// Resolve resolves the current transaction according to the rollback flag. -func Resolve(ctx context.Context) { - log := logger.NewOCMLogger(ctx) - tx, ok := dbContext.Transaction(ctx) - if !ok { - log.Error("Could not retrieve transaction from context") - return - } - - if tx.MarkedForRollback() { - if err := tx.Rollback(); err != nil { - log.Extra("error", err.Error()).Error("Could not rollback transaction") - return - } - log.Infof("Rolled back transaction") - } else { - if err := tx.Commit(); err != nil { - // TODO: what does the user see when this occurs? seems like they will get a false positive - log.Extra("error", err.Error()).Error("Could not commit transaction") - return - } - } -} - -// MarkForRollback flags the transaction stored in the context for rollback and logs whatever error caused the rollback -func MarkForRollback(ctx context.Context, err error) { - log := logger.NewOCMLogger(ctx) - transaction, ok := dbContext.Transaction(ctx) - if !ok { - log.Error("failed to mark transaction for rollback: could not retrieve transaction from context") - return - } - transaction.SetRollbackFlag(true) - log.Infof("Marked transaction for rollback, err: %v", err) -} diff --git a/pkg/db/db_context/db_context.go b/pkg/db/db_context/db_context.go deleted file mode 100755 index 1a94e49..0000000 --- a/pkg/db/db_context/db_context.go +++ /dev/null @@ -1,35 +0,0 @@ -// Package db_context dbContext provides a wrapper around db context handling to allow access to the db context without -// requiring importing the db package, thus avoiding cyclic imports -package db_context - -import ( - "context" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/transaction" -) - -type contextKey int - -const ( - transactionKey contextKey = iota -) - -// WithTransaction adds the transaction to the context and returns a new context -func WithTransaction(ctx context.Context, tx *transaction.Transaction) context.Context { - return context.WithValue(ctx, transactionKey, tx) -} - -// Transaction extracts the transaction value from the context -func Transaction(ctx context.Context) (tx *transaction.Transaction, ok bool) { - tx, ok = ctx.Value(transactionKey).(*transaction.Transaction) - return tx, ok -} - -// TxID Return the transaction ID from the context, if it exists. If there is no transaction, ok is false. -func TxID(ctx context.Context) (id int64, ok bool) { - tx, ok := Transaction(ctx) - if !ok { - return 0, false - } - return tx.TxID(), true -} diff --git a/pkg/db/db_session/db_session.go b/pkg/db/db_session/db_session.go deleted file mode 100755 index c05baa8..0000000 --- a/pkg/db/db_session/db_session.go +++ /dev/null @@ -1,9 +0,0 @@ -package db_session - -import "sync" - -const ( - disable = "disable" -) - -var once sync.Once diff --git a/pkg/db/db_session/default.go b/pkg/db/db_session/default.go deleted file mode 100755 index 563635b..0000000 --- a/pkg/db/db_session/default.go +++ /dev/null @@ -1,166 +0,0 @@ -package db_session - -import ( - "context" - "database/sql" - "fmt" - "time" - - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" - - "github.com/lib/pq" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - ocmlogger "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" -) - -type Default struct { - config *config.DatabaseConfig - - g2 *gorm.DB - // Direct database connection. - // It is used: - // - to setup/close connection because GORM V2 removed gorm.Close() - // - to work with pq.CopyIn because connection returned by GORM V2 gorm.DB() in "not the same" - db *sql.DB -} - -var _ db.SessionFactory = &Default{} - -func NewProdFactory(config *config.DatabaseConfig) *Default { - conn := &Default{} - conn.Init(config) - return conn -} - -// Init will initialize a singleton connection as needed and return the same instance. -// Go includes database connection pooling in the platform. Gorm uses the same and provides a method to -// clone a connection via New(), which is safe for use by concurrent Goroutines. -func (f *Default) Init(config *config.DatabaseConfig) { - // Only the first time - once.Do(func() { - var ( - dbx *sql.DB - g2 *gorm.DB - err error - ) - - // Open connection to DB via standard library - dbx, err = sql.Open(config.Dialect, config.ConnectionString(config.SSLMode != disable)) - if err != nil { - dbx, err = sql.Open(config.Dialect, config.ConnectionString(false)) - if err != nil { - panic(fmt.Sprintf( - "SQL failed to connect to %s database %s with connection string: %s\nError: %s", - config.Dialect, - config.Name, - config.LogSafeConnectionString(config.SSLMode != disable), - err.Error(), - )) - } - } - dbx.SetMaxOpenConns(config.MaxOpenConnections) - - // Connect GORM to use the same connection - conf := &gorm.Config{ - PrepareStmt: false, - FullSaveAssociations: false, - } - g2, err = gorm.Open(postgres.New(postgres.Config{ - Conn: dbx, - // Disable implicit prepared statement usage (GORM V2 uses pgx as database/sql driver and it enables prepared - /// statement cache by default) - // In migrations we both change tables' structure and running SQLs to modify data. - // This way all prepared statements becomes invalid. - PreferSimpleProtocol: true, - }), conf) - if err != nil { - panic(fmt.Sprintf( - "GORM failed to connect to %s database %s with connection string: %s\nError: %s", - config.Dialect, - config.Name, - config.LogSafeConnectionString(config.SSLMode != disable), - err.Error(), - )) - } - - f.config = config - f.g2 = g2 - f.db = dbx - }) -} - -func (f *Default) DirectDB() *sql.DB { - return f.db -} - -func waitForNotification(l *pq.Listener, callback func(id string)) { - logger := ocmlogger.NewOCMLogger(context.Background()) - for { - select { - case n := <-l.Notify: - logger.Infof("Received data from channel [%s] : %s", n.Channel, n.Extra) - callback(n.Extra) - return - case <-time.After(10 * time.Second): - logger.V(10).Infof("Received no events on channel during interval. Pinging source") - go func() { - l.Ping() - }() - return - } - } -} - -func newListener(ctx context.Context, connstr, channel string, callback func(id string)) { - logger := ocmlogger.NewOCMLogger(ctx) - - plog := func(ev pq.ListenerEventType, err error) { - if err != nil { - logger.Error(err.Error()) - } - } - listener := pq.NewListener(connstr, 10*time.Second, time.Minute, plog) - err := listener.Listen(channel) - if err != nil { - panic(err) - } - - logger.Infof("Starting channeling monitor for %s", channel) - for { - waitForNotification(listener, callback) - } -} - -func (f *Default) NewListener(ctx context.Context, channel string, callback func(id string)) { - newListener(ctx, f.config.ConnectionString(true), channel, callback) -} - -func (f *Default) New(ctx context.Context) *gorm.DB { - conn := f.g2.Session(&gorm.Session{ - Context: ctx, - Logger: f.g2.Logger.LogMode(logger.Silent), - }) - if f.config.Debug { - conn = conn.Debug() - } - return conn -} - -func (f *Default) CheckConnection() error { - return f.g2.Exec("SELECT 1").Error -} - -// Close will close the connection to the database. -// THIS MUST **NOT** BE CALLED UNTIL THE SERVER/PROCESS IS EXITING!! -// This should only ever be called once for the entire duration of the application and only at the end. -func (f *Default) Close() error { - return f.db.Close() -} - -func (f *Default) ResetDB() { - panic("ResetDB is not implemented for non-integration-test env") -} diff --git a/pkg/db/db_session/test.go b/pkg/db/db_session/test.go deleted file mode 100755 index 660a9d4..0000000 --- a/pkg/db/db_session/test.go +++ /dev/null @@ -1,223 +0,0 @@ -package db_session - -import ( - "context" - "database/sql" - "fmt" - "time" - - "github.com/golang/glog" - "github.com/lib/pq" - - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" -) - -type Test struct { - config *config.DatabaseConfig - g2 *gorm.DB - // Direct database connection. - // It is used: - // - to setup/close connection because GORM V2 removed gorm.Close() - // - to work with pq.CopyIn because connection returned by GORM V2 gorm.DB() in "not the same" - db *sql.DB - - wasDisconnected bool -} - -var _ db.SessionFactory = &Test{} - -func NewTestFactory(config *config.DatabaseConfig) *Test { - conn := &Test{} - conn.Init(config) - return conn -} - -// The approach: -// Every new Postgres database is implicitly copied from a template database `template1`. Any changes to template1 -// are then copied to a new database, and this copy is a cheap filesystem operation. - -// Init will: -// - initialize a template1 DB with migrations -// - rebuild AMS DB from template1 -// - return a new connection factory -// Go includes database connection pooling in the platform. Gorm uses the same and provides a method to -// clone a connection via New(), which is safe for use by concurrent Goroutines. -func (f *Test) Init(config *config.DatabaseConfig) { - // Only the first time - once.Do(func() { - if err := initDatabase(config, db.Migrate); err != nil { - glog.Errorf("error initializing test database: %s", err) - return - } - - if err := resetDB(config); err != nil { - glog.Errorf("error resetting test database: %s", err) - return - } - }) - - f.config = config - f.db, f.g2 = connectFactory(config) -} - -func initDatabase(config *config.DatabaseConfig, migrate func(db2 *gorm.DB) error) error { - // - Connect to `template1` DB - dbx, g2, cleanup := connect("template1", config) - defer cleanup() - - for _, err := dbx.Exec(`select 1`); err != nil; { - time.Sleep(100 * time.Millisecond) - } - - // - Run migrations - return migrate(g2) -} - -func resetDB(config *config.DatabaseConfig) error { - // Reconnect to the default `postgres` database, so we can drop the existing db and recreate it - dbx, _, cleanup := connect("postgres", config) - defer cleanup() - - // Drop `all` connections to both `template1` and AMS DB, so it can be dropped and created - if err := dropConnections(dbx, "template1"); err != nil { - return err - } - if err := dropConnections(dbx, config.Name); err != nil { - return err - } - - // Rebuild AMS DB - query := fmt.Sprintf("DROP DATABASE IF EXISTS %s", pq.QuoteIdentifier(config.Name)) - if _, err := dbx.Exec(query); err != nil { - return fmt.Errorf("SQL failed to DROP database %s: %s", config.Name, err.Error()) - } - query = fmt.Sprintf("CREATE DATABASE %s TEMPLATE template1", pq.QuoteIdentifier(config.Name)) - if _, err := dbx.Exec(query); err != nil { - return fmt.Errorf("SQL failed to CREATE database %s: %s", config.Name, err.Error()) - } - // As `template1` had all migrations, so now AMS DB has them too! - return nil -} - -// connect to database specified by `name` and return connections + cleanup function -func connect(name string, config *config.DatabaseConfig) (*sql.DB, *gorm.DB, func()) { - var ( - dbx *sql.DB - g2 *gorm.DB - err error - ) - - dbx, err = sql.Open(config.Dialect, config.ConnectionStringWithName(name, config.SSLMode != disable)) - if err != nil { - dbx, err = sql.Open(config.Dialect, config.ConnectionStringWithName(name, false)) - if err != nil { - panic(fmt.Sprintf( - "SQL failed to connect to %s database %s with connection string: %s\nError: %s", - config.Dialect, - name, - config.LogSafeConnectionStringWithName(name, config.SSLMode != disable), - err.Error(), - )) - } - } - - // Connect GORM to use the same connection - conf := &gorm.Config{ - PrepareStmt: false, - FullSaveAssociations: false, - SkipDefaultTransaction: true, - Logger: logger.Default.LogMode(logger.Silent), - } - g2, err = gorm.Open(postgres.New(postgres.Config{ - Conn: dbx, - // Disable implicit prepared statement usage (GORM V2 uses pgx as database/sql driver and it enables prepared - // statement cache by default) - // In migrations we both change tables' structure and running SQLs to modify data. - // This way all prepared statements becomes invalid. - PreferSimpleProtocol: true, - }), conf) - if err != nil { - panic(fmt.Sprintf( - "GORM failed to connect to %s database %s with connection string: %s\nError: %s", - config.Dialect, - config.Name, - config.LogSafeConnectionString(config.SSLMode != disable), - err.Error(), - )) - } - - return dbx, g2, func() { - if err := dbx.Close(); err != nil { - panic(err) - } - } -} - -// KILL all connections to the specified DB -func dropConnections(dbx *sql.DB, name string) error { - query := ` - select pg_terminate_backend(pg_stat_activity.pid) - from pg_stat_activity - where pg_stat_activity.datname = $1 and pid <> pg_backend_pid()` - _, err := dbx.Exec(query, name) - if err != nil { - return err - } - return nil -} - -func connectFactory(config *config.DatabaseConfig) (*sql.DB, *gorm.DB) { - var ( - dbx *sql.DB - g2 *gorm.DB - ) - dbx, g2, _ = connect(config.Name, config) - dbx.SetMaxOpenConns(config.MaxOpenConnections) - - return dbx, g2 -} - -func (f *Test) DirectDB() *sql.DB { - return f.db -} - -func (f *Test) New(ctx context.Context) *gorm.DB { - if f.wasDisconnected { - // Connection was killed in order to reset DB - f.db, f.g2 = connectFactory(f.config) - f.wasDisconnected = false - } - - conn := f.g2.Session(&gorm.Session{ - Context: ctx, - Logger: f.g2.Logger.LogMode(logger.Silent), - }) - if f.config.Debug { - conn = conn.Debug() - } - return conn -} - -// CheckConnection checks to ensure a connection is present -func (f *Test) CheckConnection() error { - _, err := f.db.Exec("SELECT 1") - return err -} - -func (f *Test) Close() error { - return f.db.Close() -} - -func (f *Test) ResetDB() { - resetDB(f.config) - f.wasDisconnected = true -} - -func (f *Test) NewListener(ctx context.Context, channel string, callback func(id string)) { - newListener(ctx, f.config.ConnectionString(true), channel, callback) -} diff --git a/pkg/db/db_session/testcontainer.go b/pkg/db/db_session/testcontainer.go deleted file mode 100755 index f454a1c..0000000 --- a/pkg/db/db_session/testcontainer.go +++ /dev/null @@ -1,175 +0,0 @@ -package db_session - -import ( - "context" - "database/sql" - "fmt" - "time" - - "github.com/golang/glog" - "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/modules/postgres" - "github.com/testcontainers/testcontainers-go/wait" - - gormpostgres "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" -) - -type Testcontainer struct { - config *config.DatabaseConfig - container *postgres.PostgresContainer - g2 *gorm.DB - sqlDB *sql.DB -} - -var _ db.SessionFactory = &Testcontainer{} - -// NewTestcontainerFactory creates a SessionFactory using testcontainers. -// This starts a real PostgreSQL container for integration testing. -func NewTestcontainerFactory(config *config.DatabaseConfig) *Testcontainer { - conn := &Testcontainer{ - config: config, - } - conn.Init(config) - return conn -} - -func (f *Testcontainer) Init(config *config.DatabaseConfig) { - ctx := context.Background() - - glog.Infof("Starting PostgreSQL testcontainer...") - - // Create PostgreSQL container - container, err := postgres.Run(ctx, - "postgres:14.2", - postgres.WithDatabase(config.Name), - postgres.WithUsername(config.Username), - postgres.WithPassword(config.Password), - testcontainers.WithWaitStrategy( - wait.ForLog("database system is ready to accept connections"). - WithOccurrence(2). - WithStartupTimeout(60*time.Second)), - ) - if err != nil { - glog.Fatalf("Failed to start PostgreSQL testcontainer: %s", err) - } - - f.container = container - - // Get connection string from container - connStr, err := container.ConnectionString(ctx, "sslmode=disable") - if err != nil { - glog.Fatalf("Failed to get connection string from testcontainer: %s", err) - } - - glog.Infof("PostgreSQL testcontainer started at: %s", connStr) - - // Open SQL connection - f.sqlDB, err = sql.Open("postgres", connStr) - if err != nil { - glog.Fatalf("Failed to connect to testcontainer database: %s", err) - } - - // Configure connection pool - f.sqlDB.SetMaxOpenConns(config.MaxOpenConnections) - - // Connect GORM to use the same connection - conf := &gorm.Config{ - PrepareStmt: false, - FullSaveAssociations: false, - SkipDefaultTransaction: true, - Logger: logger.Default.LogMode(logger.Silent), - } - - if config.Debug { - conf.Logger = logger.Default.LogMode(logger.Info) - } - - f.g2, err = gorm.Open(gormpostgres.New(gormpostgres.Config{ - Conn: f.sqlDB, - PreferSimpleProtocol: true, - }), conf) - if err != nil { - glog.Fatalf("Failed to connect GORM to testcontainer database: %s", err) - } - - // Run migrations - glog.Infof("Running database migrations on testcontainer...") - if err := db.Migrate(f.g2); err != nil { - glog.Fatalf("Failed to run migrations on testcontainer: %s", err) - } - - glog.Infof("Testcontainer database initialized successfully") -} - -func (f *Testcontainer) DirectDB() *sql.DB { - return f.sqlDB -} - -func (f *Testcontainer) New(ctx context.Context) *gorm.DB { - conn := f.g2.Session(&gorm.Session{ - Context: ctx, - Logger: f.g2.Logger.LogMode(logger.Silent), - }) - if f.config.Debug { - conn = conn.Debug() - } - return conn -} - -func (f *Testcontainer) CheckConnection() error { - _, err := f.sqlDB.Exec("SELECT 1") - return err -} - -func (f *Testcontainer) Close() error { - ctx := context.Background() - - // Close SQL connection - if f.sqlDB != nil { - if err := f.sqlDB.Close(); err != nil { - glog.Errorf("Error closing SQL connection: %s", err) - } - } - - // Terminate container - if f.container != nil { - glog.Infof("Stopping PostgreSQL testcontainer...") - if err := f.container.Terminate(ctx); err != nil { - return fmt.Errorf("failed to terminate testcontainer: %s", err) - } - glog.Infof("PostgreSQL testcontainer stopped") - } - - return nil -} - -func (f *Testcontainer) ResetDB() { - // For testcontainers, we can just truncate all tables - ctx := context.Background() - g2 := f.New(ctx) - - tables := []string{"dinosaurs", "events", "registry_credentials", "access_tokens", "registrys"} - for _, table := range tables { - if g2.Migrator().HasTable(table) { - if err := g2.Exec(fmt.Sprintf("TRUNCATE TABLE %s CASCADE", table)).Error; err != nil { - glog.Errorf("Error truncating table %s: %s", table, err) - } - } - } -} - -func (f *Testcontainer) NewListener(ctx context.Context, channel string, callback func(id string)) { - // Get the connection string for the listener - connStr, err := f.container.ConnectionString(ctx, "sslmode=disable") - if err != nil { - glog.Errorf("Failed to get connection string for listener: %s", err) - return - } - - newListener(ctx, connStr, channel, callback) -} diff --git a/pkg/db/metrics_collector.go b/pkg/db/metrics_collector.go deleted file mode 100755 index 80af075..0000000 --- a/pkg/db/metrics_collector.go +++ /dev/null @@ -1,100 +0,0 @@ -package db - -import ( - "time" - - "github.com/prometheus/client_golang/prometheus" -) - -type MetricsCollector interface { -} - -// Subsystem used to define the metrics: -const metricsSubsystem = "advisory_lock" - -// Names of the labels added to metrics: -const ( - metricsTypeLabel = "type" - metricsStatusLabel = "status" -) - -// MetricsLabels - Array of labels added to metrics: -var MetricsLabels = []string{ - metricsTypeLabel, - metricsStatusLabel, -} - -// Names of the metrics: -const ( - countMetric = "count" - durationMetric = "duration" -) - -// MetricsNames - Array of Names of the metrics: -var MetricsNames = []string{ - countMetric, - durationMetric, -} - -// Description of the requests count metric: -var advisoryLockCountMetric = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Subsystem: metricsSubsystem, - Name: countMetric, - Help: "Number of advisory lock requests.", - }, - MetricsLabels, -) - -// Description of the request duration metric: -var advisoryLockDurationMetric = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Subsystem: metricsSubsystem, - Name: durationMetric, - Help: "Advisory Lock durations in seconds.", - Buckets: []float64{ - 0.1, - 0.2, - 0.5, - 1.0, - 2.0, - 10.0, - }, - }, - MetricsLabels, -) - -// RegisterAdvisoryLockMetrics Register the metrics: -func RegisterAdvisoryLockMetrics() { - prometheus.MustRegister(advisoryLockCountMetric) - prometheus.MustRegister(advisoryLockDurationMetric) -} - -// UnregisterAdvisoryLockMetrics Unregister the metrics: -func UnregisterAdvisoryLockMetrics() { - prometheus.Unregister(advisoryLockCountMetric) - prometheus.Unregister(advisoryLockDurationMetric) -} - -// ResetAdvisoryLockMetricsCollectors resets all collectors -func ResetAdvisoryLockMetricsCollectors() { - advisoryLockCountMetric.Reset() - advisoryLockDurationMetric.Reset() -} - -func UpdateAdvisoryLockCountMetric(lockType LockType, status string) { - labels := prometheus.Labels{ - metricsTypeLabel: string(lockType), - metricsStatusLabel: status, - } - advisoryLockCountMetric.With(labels).Inc() -} - -func UpdateAdvisoryLockDurationMetric(lockType LockType, status string, startTime time.Time) { - labels := prometheus.Labels{ - metricsTypeLabel: string(lockType), - metricsStatusLabel: status, - } - duration := time.Since(startTime) - advisoryLockDurationMetric.With(labels).Observe(duration.Seconds()) -} diff --git a/pkg/db/migrations.go b/pkg/db/migrations.go deleted file mode 100755 index f1e5a14..0000000 --- a/pkg/db/migrations.go +++ /dev/null @@ -1,39 +0,0 @@ -package db - -import ( - "context" - - "github.com/go-gormigrate/gormigrate/v2" - "github.com/golang/glog" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/migrations" - - "gorm.io/gorm" -) - -// gormigrate is a wrapper for gorm's migration functions that adds schema versioning and rollback capabilities. -// For help writing migration steps, see the gorm documentation on migrations: http://doc.gorm.io/database.html#migration - -func Migrate(g2 *gorm.DB) error { - m := newGormigrate(g2) - - if err := m.Migrate(); err != nil { - return err - } - return nil -} - -// MigrateTo a specific migration will not seed the database, seeds are up to date with the latest -// schema based on the most recent migration -// This should be for testing purposes mainly -func MigrateTo(sessionFactory SessionFactory, migrationID string) { - g2 := sessionFactory.New(context.Background()) - m := newGormigrate(g2) - - if err := m.MigrateTo(migrationID); err != nil { - glog.Fatalf("Could not migrate: %v", err) - } -} - -func newGormigrate(g2 *gorm.DB) *gormigrate.Gormigrate { - return gormigrate.New(g2, gormigrate.DefaultOptions, migrations.MigrationList) -} diff --git a/pkg/db/migrations/201911212019_add_dinosaurs.go b/pkg/db/migrations/201911212019_add_dinosaurs.go deleted file mode 100755 index b425824..0000000 --- a/pkg/db/migrations/201911212019_add_dinosaurs.go +++ /dev/null @@ -1,29 +0,0 @@ -package migrations - -// Migrations should NEVER use types from other packages. Types can change -// and then migrations run on a _new_ database will fail or behave unexpectedly. -// Instead of importing types, always re-create the type in the migration, as -// is done here, even though the same type is defined in pkg/api - -import ( - "gorm.io/gorm" - - "github.com/go-gormigrate/gormigrate/v2" -) - -func addDinosaurs() *gormigrate.Migration { - type Dinosaur struct { - Model - Species string `gorm:"index"` - } - - return &gormigrate.Migration{ - ID: "201911212019", - Migrate: func(tx *gorm.DB) error { - return tx.AutoMigrate(&Dinosaur{}) - }, - Rollback: func(tx *gorm.DB) error { - return tx.Migrator().DropTable(&Dinosaur{}) - }, - } -} diff --git a/pkg/db/migrations/202309020925_add_events.go b/pkg/db/migrations/202309020925_add_events.go deleted file mode 100755 index 3cc3728..0000000 --- a/pkg/db/migrations/202309020925_add_events.go +++ /dev/null @@ -1,31 +0,0 @@ -package migrations - -import ( - "time" - - "gorm.io/gorm" - - "github.com/go-gormigrate/gormigrate/v2" -) - -func addEvents() *gormigrate.Migration { - type Event struct { - Model - Source string `gorm:"index"` // MyTable, any string - // SourceID must be an indexable key for querying, *not* a json data payload. - // an indexed column of data json data would explode - SourceID string `gorm:"index"` // primary key of MyTable - EventType string // Add|Update|Delete, any string - ReconciledDate *time.Time `gorm:"null;index"` - } - - return &gormigrate.Migration{ - ID: "202309020925", - Migrate: func(tx *gorm.DB) error { - return tx.AutoMigrate(&Event{}) - }, - Rollback: func(tx *gorm.DB) error { - return tx.Migrator().DropTable(&Event{}) - }, - } -} diff --git a/pkg/db/migrations/migration_structs.go b/pkg/db/migrations/migration_structs.go deleted file mode 100755 index 1bd3671..0000000 --- a/pkg/db/migrations/migration_structs.go +++ /dev/null @@ -1,64 +0,0 @@ -package migrations - -import ( - "fmt" - "time" - - "gorm.io/gorm" - - "github.com/go-gormigrate/gormigrate/v2" -) - -// gormigrate is a wrapper for gorm's migration functions that adds schema versioning and rollback capabilities. -// For help writing migration steps, see the gorm documentation on migrations: http://doc.gorm.io/database.html#migration - -// MigrationList rules: -// -// 1. IDs are numerical timestamps that must sort ascending. -// Use YYYYMMDDHHMM w/ 24 hour time for format -// Example: August 21 2018 at 2:54pm would be 201808211454. -// -// 2. Include models inline with migrations to see the evolution of the object over time. -// Using our internal type models directly in the first migration would fail in future clean installs. -// -// 3. Migrations must be backwards compatible. There are no new required fields allowed. -// See $project_home/g2/README.md -// -// 4. Create one function in a separate file that returns your Migration. Add that single function call to this list. -var MigrationList = []*gormigrate.Migration{ - addDinosaurs(), - addEvents(), - addRegistryCredentials(), - addAccessTokens(), - addRegistrys(), -} - -// Model represents the base model struct. All entities will have this struct embedded. -type Model struct { - ID string `gorm:"primary_key"` - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt gorm.DeletedAt `gorm:"index"` -} - -type fkMigration struct { - Model string - Dest string - Field string - Reference string -} - -func CreateFK(g2 *gorm.DB, fks ...fkMigration) error { - var query = `ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s ON DELETE RESTRICT ON UPDATE RESTRICT;` - var drop = `ALTER TABLE %s DROP CONSTRAINT IF EXISTS %s;` - - for _, fk := range fks { - name := fmt.Sprintf("fk_%s_%s", fk.Model, fk.Dest) - - g2.Exec(fmt.Sprintf(drop, fk.Model, name)) - if err := g2.Exec(fmt.Sprintf(query, fk.Model, name, fk.Field, fk.Reference)).Error; err != nil { - return err - } - } - return nil -} diff --git a/pkg/db/mocks/advisory_locks.go b/pkg/db/mocks/advisory_locks.go deleted file mode 100755 index 150fc55..0000000 --- a/pkg/db/mocks/advisory_locks.go +++ /dev/null @@ -1,49 +0,0 @@ -package mocks - -import ( - "context" - "fmt" - - "github.com/google/uuid" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" -) - -type MockAdvisoryLockFactory struct { - locks map[string]string -} - -func NewMockAdvisoryLockFactory() *MockAdvisoryLockFactory { - return &MockAdvisoryLockFactory{ - locks: make(map[string]string), - } -} - -func (f *MockAdvisoryLockFactory) NewAdvisoryLock(ctx context.Context, id string, lockType db.LockType) (string, error) { - lockOwnerID := uuid.New().String() - key := fmt.Sprintf("%s-%s", id, lockType) - if _, ok := f.locks[key]; ok { - return lockOwnerID, nil - } - - f.locks[key] = lockOwnerID - return lockOwnerID, nil -} - -func (f *MockAdvisoryLockFactory) NewNonBlockingLock(ctx context.Context, id string, lockType db.LockType) (string, bool, error) { - lockOwnerID := uuid.New().String() - key := fmt.Sprintf("%s-%s", id, lockType) - if _, ok := f.locks[key]; ok { - return lockOwnerID, true, nil - } - - f.locks[key] = lockOwnerID - return lockOwnerID, true, nil -} - -func (f *MockAdvisoryLockFactory) Unlock(ctx context.Context, uuid string) { - for k, v := range f.locks { - if v == uuid { - delete(f.locks, k) - } - } -} diff --git a/pkg/db/mocks/session_factory.go b/pkg/db/mocks/session_factory.go deleted file mode 100755 index 34ec5cc..0000000 --- a/pkg/db/mocks/session_factory.go +++ /dev/null @@ -1,79 +0,0 @@ -package mocks - -import ( - "context" - "database/sql" - - "github.com/DATA-DOG/go-sqlmock" - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" -) - -var _ db.SessionFactory = &MockSessionFactory{} - -type MockSessionFactory struct { - gormDB *gorm.DB - sqlDB *sql.DB - mock sqlmock.Sqlmock -} - -// NewMockSessionFactory creates a SessionFactory using go-sqlmock. -// This provides a mock database without requiring PostgreSQL or SQLite. -func NewMockSessionFactory() *MockSessionFactory { - // Create mock SQL database - sqlDB, mock, err := sqlmock.New() - if err != nil { - panic("failed to create sqlmock: " + err.Error()) - } - - // Open GORM with the mock database using postgres dialector - gormDB, err := gorm.Open(postgres.New(postgres.Config{ - Conn: sqlDB, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Silent), - }) - if err != nil { - panic("failed to create gorm DB with sqlmock: " + err.Error()) - } - - return &MockSessionFactory{ - gormDB: gormDB, - sqlDB: sqlDB, - mock: mock, - } -} - -func (m *MockSessionFactory) Init(config *config.DatabaseConfig) { - // Mock implementation - does nothing -} - -func (m *MockSessionFactory) DirectDB() *sql.DB { - return m.sqlDB -} - -func (m *MockSessionFactory) New(ctx context.Context) *gorm.DB { - return m.gormDB.WithContext(ctx) -} - -func (m *MockSessionFactory) CheckConnection() error { - return nil -} - -func (m *MockSessionFactory) Close() error { - if m.sqlDB != nil { - return m.sqlDB.Close() - } - return nil -} - -func (m *MockSessionFactory) ResetDB() { - // Mock implementation - does nothing -} - -func (m *MockSessionFactory) NewListener(ctx context.Context, channel string, callback func(id string)) { - // Mock implementation - does nothing -} diff --git a/pkg/db/session.go b/pkg/db/session.go deleted file mode 100755 index 675f38f..0000000 --- a/pkg/db/session.go +++ /dev/null @@ -1,20 +0,0 @@ -package db - -import ( - "context" - "database/sql" - - "gorm.io/gorm" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" -) - -type SessionFactory interface { - Init(*config.DatabaseConfig) - DirectDB() *sql.DB - New(ctx context.Context) *gorm.DB - CheckConnection() error - Close() error - ResetDB() - NewListener(ctx context.Context, channel string, callback func(id string)) -} diff --git a/pkg/db/sql_helpers.go b/pkg/db/sql_helpers.go deleted file mode 100755 index b0d0f17..0000000 --- a/pkg/db/sql_helpers.go +++ /dev/null @@ -1,249 +0,0 @@ -package db - -import ( - "fmt" - "reflect" - "strings" - - "github.com/jinzhu/inflection" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" - "github.com/yaacov/tree-search-language/pkg/tsl" - "gorm.io/gorm" -) - -// Check if a field name starts with properties. -func startsWithProperties(s string) bool { - return strings.HasPrefix(s, "properties.") -} - -// hasProperty return true if node has a property identifier on left hand side. -func hasProperty(n tsl.Node) bool { - // Get the left side operator. - l, ok := n.Left.(tsl.Node) - if !ok { - return false - } - - // If left side hand is not a `properties` identifier, return. - if l.Func != tsl.IdentOp || !startsWithProperties(l.Left.(string)) { - return false - } - - return true -} - -// getField gets the sql field associated with a name. -func getField(name string, disallowedFields map[string]string) (field string, err *errors.ServiceError) { - // We want to accept names with trailing and leading spaces - trimmedName := strings.Trim(name, " ") - - // Check for properties ->> '' - if strings.HasPrefix(trimmedName, "properties ->>") { - field = trimmedName - return - } - - // Check for nested field, e.g., subscription_labels.key - checkName := trimmedName - fieldParts := strings.Split(trimmedName, ".") - if len(fieldParts) > 2 { - err = errors.BadRequest("%s is not a valid field name", name) - return - } - if len(fieldParts) > 1 { - checkName = fieldParts[1] - } - - // Check for allowed fields - _, ok := disallowedFields[checkName] - if ok { - err = errors.BadRequest("%s is not a valid field name", name) - return - } - field = trimmedName - return -} - -// propertiesNodeConverter converts a node with a properties identifier -// to a properties node. -// -// For example, it will convert: -// ( properties. = ) to -// ( properties ->> = ) -func propertiesNodeConverter(n tsl.Node) tsl.Node { - - // Get the left side operator. - l, ok := n.Left.(tsl.Node) - if !ok { - return n - } - - // Get the property name. - propetyName := l.Left.(string)[11:] - - // Build a new node that converts: - // ( properties. = ) to - // ( properties ->> = ) - propertyNode := tsl.Node{ - Func: n.Func, - Left: tsl.Node{ - Func: tsl.IdentOp, - Left: fmt.Sprintf("properties ->> '%s'", propetyName), - }, - Right: n.Right, - } - - return propertyNode -} - -// FieldNameWalk walks on the filter tree and check/replace -// the search fields names: -// a. the the field name is valid. -// b. replace the field name with the SQL column name. -func FieldNameWalk( - n tsl.Node, - disallowedFields map[string]string) (newNode tsl.Node, err *errors.ServiceError) { - - var field string - var l, r tsl.Node - - // Check for properties. = nodes, and convert them to - // ( properties ->> = ) - // nodes. - if hasProperty(n) { - n = propertiesNodeConverter(n) - } - - switch n.Func { - case tsl.IdentOp: - // If this is an Identifier, check field name is a string. - userFieldName, ok := n.Left.(string) - if !ok { - err = errors.BadRequest("Identifier name must be a string") - return - } - - // Check field name in the disallowedFields field names. - field, err = getField(userFieldName, disallowedFields) - if err != nil { - return - } - - // Replace identifier name. - newNode = tsl.Node{Func: tsl.IdentOp, Left: field} - case tsl.StringOp, tsl.NumberOp: - // This are leafs, just return. - newNode = tsl.Node{Func: n.Func, Left: n.Left} - default: - // o/w continue walking the tree. - if n.Left != nil { - l, err = FieldNameWalk(n.Left.(tsl.Node), disallowedFields) - if err != nil { - return - } - } - - // Add right child(ren) if exist. - if n.Right != nil { - switch v := n.Right.(type) { - case tsl.Node: - // It's a regular node, just add it. - r, err = FieldNameWalk(v, disallowedFields) - if err != nil { - return - } - - newNode = tsl.Node{Func: n.Func, Left: l, Right: r} - case []tsl.Node: - // It's a list of nodes, some TSL operators have multiple RHS arguments - // for example `IN` and `BETWEEN`. If this operator has a list of arguments, - // loop over the list, and add all nodes. - var rr []tsl.Node - - // Add all nodes in the right side array. - for _, e := range v { - r, err = FieldNameWalk(e, disallowedFields) - if err != nil { - return - } - - rr = append(rr, r) - } - - newNode = tsl.Node{Func: n.Func, Left: l, Right: rr} - default: - // We only support `Node` and `[]Node` types for the right hand side, - // of TSL operators. If here than this is an unsupported right hand side - // type. - err = errors.BadRequest("unsupported right hand side type in search query") - } - } else { - // If here than `n.Right` is nil. This is a legit type of node, - // we just need to ignore the right hand side, and continue walking the - // tree. - newNode = tsl.Node{Func: n.Func, Left: l} - } - } - - return -} - -// cleanOrderBy takes the orderBy arg and cleans it. -func cleanOrderBy(userArg string, disallowedFields map[string]string) (orderBy string, err *errors.ServiceError) { - var orderField string - - // We want to accept user params with trailing and leading spaces - trimedName := strings.Trim(userArg, " ") - - // Each OrderBy can be a "" or a " asc|desc" - order := strings.Split(trimedName, " ") - direction := "none valid" - - if len(order) == 1 { - orderField, err = getField(order[0], disallowedFields) - direction = "asc" - } else if len(order) == 2 { - orderField, err = getField(order[0], disallowedFields) - direction = order[1] - } - if err != nil || (direction != "asc" && direction != "desc") { - err = errors.BadRequest("bad order value '%s'", userArg) - return - } - - orderBy = fmt.Sprintf("%s %s", orderField, direction) - return -} - -// ArgsToOrderBy returns cleaned orderBy list. -func ArgsToOrderBy( - orderByArgs []string, - disallowedFields map[string]string) (orderBy []string, err *errors.ServiceError) { - - var order string - if len(orderByArgs) != 0 { - orderBy = []string{} - for _, o := range orderByArgs { - order, err = cleanOrderBy(o, disallowedFields) - if err != nil { - return - } - - // If valid add the user entered order by, to the order by list - orderBy = append(orderBy, order) - } - } - return -} - -func GetTableName(g2 *gorm.DB) string { - if g2.Statement.Parse(g2.Statement.Model) != nil { - return "xxx" - } - if g2.Statement.Schema != nil { - return g2.Statement.Schema.Table - } else { - name := reflect.TypeOf(g2.Statement.Model).Elem().Name() - return inflection.Plural(strings.ToLower(name)) - } -} diff --git a/pkg/db/transaction/transaction.go b/pkg/db/transaction/transaction.go deleted file mode 100755 index 246fda0..0000000 --- a/pkg/db/transaction/transaction.go +++ /dev/null @@ -1,67 +0,0 @@ -package transaction - -import ( - "database/sql" - "errors" -) - -// By default do no roll back transaction. -// only perform rollback if explicitly set by g2.g2.MarkForRollback(ctx, err) -const defaultRollbackPolicy = false - -// Transaction represents an sql transaction -type Transaction struct { - rollbackFlag bool - tx *sql.Tx - txid int64 -} - -// Build Creates a new transaction object -func Build(tx *sql.Tx, id int64, rollbackFlag bool) *Transaction { - return &Transaction{ - tx: tx, - txid: id, - rollbackFlag: defaultRollbackPolicy, - } -} - -// MarkedForRollback returns true if a transaction is flagged for rollback and false otherwise. -func (tx *Transaction) MarkedForRollback() bool { - return tx.rollbackFlag -} - -func (tx *Transaction) Tx() *sql.Tx { - return tx.tx -} - -func (tx *Transaction) TxID() int64 { - return tx.txid -} - -func (tx *Transaction) Commit() error { - // tx must exits - if tx.tx == nil { - return errors.New("db: transaction hasn't been started yet") - } - - // must call commit on 'g2' which is Gorm - // do *not* call commit on the underlying transaction itself. Gorm does that. - err := tx.tx.Commit() - tx.tx = nil - return err -} - -// Rollback ends the transaction by rolling back -func (tx *Transaction) Rollback() error { - // tx must exist - if tx.tx == nil { - return errors.New("db: transaction hasn't been started yet") - } - err := tx.tx.Rollback() - tx.tx = nil - return err -} - -func (tx *Transaction) SetRollbackFlag(flag bool) { - tx.rollbackFlag = flag -} diff --git a/pkg/db/transaction_middleware.go b/pkg/db/transaction_middleware.go deleted file mode 100755 index e1725c7..0000000 --- a/pkg/db/transaction_middleware.go +++ /dev/null @@ -1,59 +0,0 @@ -package db - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/getsentry/sentry-go" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/db_context" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" -) - -// TransactionMiddleware creates a new HTTP middleware that begins a database transaction -// and stores it in the request context. -func TransactionMiddleware(next http.Handler, connection SessionFactory) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Create a new Context with the transaction stored in it. - ctx, err := NewContext(r.Context(), connection) - log := logger.NewOCMLogger(ctx) - if err != nil { - log.Extra("error", err.Error()).Error("Could not create transaction") - // use default error to avoid exposing internals to users - err := errors.GeneralError("") - operationID := logger.GetOperationID(ctx) - writeJSONResponse(w, err.HttpCode, err.AsOpenapiError(operationID)) - return - } - - // Set the value of the request pointer to the value of a new copy of the request with the new context key,vale - // stored in it - *r = *r.WithContext(ctx) - - if hub := sentry.GetHubFromContext(ctx); hub != nil { - hub.ConfigureScope(func(scope *sentry.Scope) { - if txid, ok := db_context.TxID(ctx); ok { - scope.SetTag("db_transaction_id", fmt.Sprintf("%d", txid)) - } - }) - } - - // Returned from handlers and resolve transactions. - defer func() { Resolve(r.Context()) }() - - // Continue handling requests. - next.ServeHTTP(w, r) - }) -} - -func writeJSONResponse(w http.ResponseWriter, code int, payload interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - - if payload != nil { - response, _ := json.Marshal(payload) - _, _ = w.Write(response) - } -} diff --git a/pkg/db/transactions.go b/pkg/db/transactions.go deleted file mode 100755 index 6dfaf19..0000000 --- a/pkg/db/transactions.go +++ /dev/null @@ -1,38 +0,0 @@ -package db - -import ( - "context" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/transaction" -) - -// By default do no roll back transaction. -// only perform rollback if explicitly set by g2.g2.MarkForRollback(ctx, err) -const defaultRollbackPolicy = false - -// newTransaction constructs a new Transaction object. -func newTransaction(ctx context.Context, connection SessionFactory) (*transaction.Transaction, error) { - if connection == nil { - // This happens in non-integration tests - return nil, nil - } - - dbx := connection.DirectDB() - tx, err := dbx.Begin() - if err != nil { - return nil, err - } - - // current transaction ID set by postgres. these are *not* distinct across time - // and do get reset after postgres performs "vacuuming" to reclaim used IDs. - var txid int64 - row := tx.QueryRow("select txid_current()") - if row != nil { - err := row.Scan(&txid) - if err != nil { - return nil, err - } - } - - return transaction.Build(tx, txid, defaultRollbackPolicy), nil -} diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go deleted file mode 100755 index 3162d7e..0000000 --- a/pkg/errors/errors.go +++ /dev/null @@ -1,207 +0,0 @@ -package errors - -import ( - "fmt" - "net/http" - "strconv" - - "github.com/golang/glog" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" -) - -const ( - // Prefix used for error code strings - // Example: - // ErrorCodePrefix = "rh-text" - // results in: registry-credential-service-1 - ErrorCodePrefix = "registry-credential-service" - - // HREF for API errors - ErrorHref = "/api/registry-credential-service/v1/errors/" - - // InvalidToken occurs when a token is invalid (generally, not found in the database) - ErrorInvalidToken ServiceErrorCode = 1 - - // Forbidden occurs when a user has been blacklisted - ErrorForbidden ServiceErrorCode = 4 - - // Conflict occurs when a database constraint is violated - ErrorConflict ServiceErrorCode = 6 - - // NotFound occurs when a record is not found in the database - ErrorNotFound ServiceErrorCode = 7 - - // Validation occurs when an object fails validation - ErrorValidation ServiceErrorCode = 8 - - // General occurs when an error fails to match any other error code - ErrorGeneral ServiceErrorCode = 9 - - // NotImplemented occurs when an API REST method is not implemented in a handler - ErrorNotImplemented ServiceErrorCode = 10 - - // Unauthorized occurs when the requester is not authorized to perform the specified action - ErrorUnauthorized ServiceErrorCode = 11 - - // Unauthenticated occurs when the provided credentials cannot be validated - ErrorUnauthenticated ServiceErrorCode = 15 - - // MalformedRequest occurs when the request body cannot be read - ErrorMalformedRequest ServiceErrorCode = 17 - - // Bad Request - ErrorBadRequest ServiceErrorCode = 21 - - // Invalid Search Query - ErrorFailedToParseSearch ServiceErrorCode = 23 - - // DatabaseAdvisoryLock occurs whe the advisory lock is failed to get - ErrorDatabaseAdvisoryLock ServiceErrorCode = 26 -) - -type ServiceErrorCode int - -type ServiceErrors []ServiceError - -func Find(code ServiceErrorCode) (bool, *ServiceError) { - for _, err := range Errors() { - if err.Code == code { - return true, &err - } - } - return false, nil -} - -func Errors() ServiceErrors { - return ServiceErrors{ - ServiceError{ErrorInvalidToken, "Invalid token provided", http.StatusForbidden}, - ServiceError{ErrorForbidden, "Forbidden to perform this action", http.StatusForbidden}, - ServiceError{ErrorConflict, "An entity with the specified unique values already exists", http.StatusConflict}, - ServiceError{ErrorNotFound, "Resource not found", http.StatusNotFound}, - ServiceError{ErrorValidation, "General validation failure", http.StatusBadRequest}, - ServiceError{ErrorGeneral, "Unspecified error", http.StatusInternalServerError}, - ServiceError{ErrorNotImplemented, "HTTP Method not implemented for this endpoint", http.StatusMethodNotAllowed}, - ServiceError{ErrorUnauthorized, "Account is unauthorized to perform this action", http.StatusForbidden}, - ServiceError{ErrorUnauthenticated, "Account authentication could not be verified", http.StatusUnauthorized}, - ServiceError{ErrorMalformedRequest, "Unable to read request body", http.StatusBadRequest}, - ServiceError{ErrorBadRequest, "Bad request", http.StatusBadRequest}, - ServiceError{ErrorFailedToParseSearch, "Failed to parse search query", http.StatusBadRequest}, - ServiceError{ErrorDatabaseAdvisoryLock, "Database advisory lock error", http.StatusInternalServerError}, - } -} - -type ServiceError struct { - // Code is the numeric and distinct ID for the error - Code ServiceErrorCode - // Reason is the context-specific reason the error was generated - Reason string - // HttopCode is the HttpCode associated with the error when the error is returned as an API response - HttpCode int -} - -// New Reason can be a string with format verbs, which will be replace by the specified values -func New(code ServiceErrorCode, reason string, values ...interface{}) *ServiceError { - // If the code isn't defined, use the general error code - var err *ServiceError - exists, err := Find(code) - if !exists { - glog.Errorf("Undefined error code used: %d", code) - err = &ServiceError{ErrorGeneral, "Unspecified error", 500} - } - - // If the reason is unspecified, use the default - if reason != "" { - err.Reason = fmt.Sprintf(reason, values...) - } - - return err -} - -func (e *ServiceError) Error() string { - return fmt.Sprintf("%s: %s", *CodeStr(e.Code), e.Reason) -} - -func (e *ServiceError) AsError() error { - return fmt.Errorf("%s", e.Error()) -} - -func (e *ServiceError) Is404() bool { - return e.Code == NotFound("").Code -} - -func (e *ServiceError) IsConflict() bool { - return e.Code == Conflict("").Code -} - -func (e *ServiceError) IsForbidden() bool { - return e.Code == Forbidden("").Code -} - -func (e *ServiceError) AsOpenapiError(operationID string) openapi.Error { - return openapi.Error{ - Kind: openapi.PtrString("Error"), - Id: openapi.PtrString(strconv.Itoa(int(e.Code))), - Href: Href(e.Code), - Code: CodeStr(e.Code), - Reason: openapi.PtrString(e.Reason), - OperationId: openapi.PtrString(operationID), - } -} - -func CodeStr(code ServiceErrorCode) *string { - return openapi.PtrString(fmt.Sprintf("%s-%d", ErrorCodePrefix, code)) -} - -func Href(code ServiceErrorCode) *string { - return openapi.PtrString(fmt.Sprintf("%s%d", ErrorHref, code)) -} - -func NotFound(reason string, values ...interface{}) *ServiceError { - return New(ErrorNotFound, reason, values...) -} - -func GeneralError(reason string, values ...interface{}) *ServiceError { - return New(ErrorGeneral, reason, values...) -} - -func Unauthorized(reason string, values ...interface{}) *ServiceError { - return New(ErrorUnauthorized, reason, values...) -} - -func Unauthenticated(reason string, values ...interface{}) *ServiceError { - return New(ErrorUnauthenticated, reason, values...) -} - -func Forbidden(reason string, values ...interface{}) *ServiceError { - return New(ErrorForbidden, reason, values...) -} - -func NotImplemented(reason string, values ...interface{}) *ServiceError { - return New(ErrorNotImplemented, reason, values...) -} - -func Conflict(reason string, values ...interface{}) *ServiceError { - return New(ErrorConflict, reason, values...) -} - -func Validation(reason string, values ...interface{}) *ServiceError { - return New(ErrorValidation, reason, values...) -} - -func MalformedRequest(reason string, values ...interface{}) *ServiceError { - return New(ErrorMalformedRequest, reason, values...) -} - -func BadRequest(reason string, values ...interface{}) *ServiceError { - return New(ErrorBadRequest, reason, values...) -} - -func FailedToParseSearch(reason string, values ...interface{}) *ServiceError { - message := fmt.Sprintf("Failed to parse search query: %s", reason) - return New(ErrorFailedToParseSearch, message, values...) -} - -func DatabaseAdvisoryLock(err error) *ServiceError { - return New(ErrorDatabaseAdvisoryLock, err.Error(), []string{}) -} diff --git a/pkg/errors/errors_test.go b/pkg/errors/errors_test.go deleted file mode 100755 index a6fe11d..0000000 --- a/pkg/errors/errors_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package errors - -import ( - "testing" - - . "github.com/onsi/gomega" -) - -func TestErrorFormatting(t *testing.T) { - RegisterTestingT(t) - err := New(ErrorGeneral, "test %s, %d", "errors", 1) - Expect(err.Reason).To(Equal("test errors, 1")) -} - -func TestErrorFind(t *testing.T) { - RegisterTestingT(t) - exists, err := Find(ErrorNotFound) - Expect(exists).To(Equal(true)) - Expect(err.Code).To(Equal(ErrorNotFound)) - - // Hopefully we never reach 91,823,719 error codes or this test will fail - exists, err = Find(ServiceErrorCode(91823719)) - Expect(exists).To(Equal(false)) - Expect(err).To(BeNil()) -} diff --git a/pkg/handlers/dinosaur.go b/pkg/handlers/dinosaur.go deleted file mode 100755 index 53953d0..0000000 --- a/pkg/handlers/dinosaur.go +++ /dev/null @@ -1,145 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/gorilla/mux" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/presenters" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" -) - -var _ RestHandler = dinosaurHandler{} - -type dinosaurHandler struct { - dinosaur services.DinosaurService - generic services.GenericService -} - -func NewDinosaurHandler(dinosaur services.DinosaurService, generic services.GenericService) *dinosaurHandler { - return &dinosaurHandler{ - dinosaur: dinosaur, - generic: generic, - } -} - -func (h dinosaurHandler) Create(w http.ResponseWriter, r *http.Request) { - var dinosaur openapi.Dinosaur - cfg := &handlerConfig{ - &dinosaur, - []validate{ - validateEmpty(&dinosaur, "Id", "id"), - validateNotEmpty(&dinosaur, "Species", "species"), - }, - func() (interface{}, *errors.ServiceError) { - ctx := r.Context() - dino := presenters.ConvertDinosaur(dinosaur) - dino, err := h.dinosaur.Create(ctx, dino) - if err != nil { - return nil, err - } - return presenters.PresentDinosaur(dino), nil - }, - handleError, - } - - handle(w, r, cfg, http.StatusCreated) -} - -func (h dinosaurHandler) Patch(w http.ResponseWriter, r *http.Request) { - var patch openapi.DinosaurPatchRequest - - cfg := &handlerConfig{ - &patch, - []validate{ - validateDinosaurPatch(&patch), - }, - func() (interface{}, *errors.ServiceError) { - ctx := r.Context() - id := mux.Vars(r)["id"] - dino, err := h.dinosaur.Replace(ctx, &api.Dinosaur{ - Meta: api.Meta{ID: id}, - Species: *patch.Species, - }) - if err != nil { - return nil, err - } - return presenters.PresentDinosaur(dino), nil - }, - handleError, - } - - handle(w, r, cfg, http.StatusOK) -} - -func (h dinosaurHandler) List(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ - Action: func() (interface{}, *errors.ServiceError) { - ctx := r.Context() - - listArgs := services.NewListArguments(r.URL.Query()) - var dinosaurs []api.Dinosaur - paging, err := h.generic.List(ctx, "username", listArgs, &dinosaurs) - if err != nil { - return nil, err - } - dinoList := openapi.DinosaurList{ - Kind: "DinosaurList", - Page: int32(paging.Page), - Size: int32(paging.Size), - Total: int32(paging.Total), - Items: []openapi.Dinosaur{}, - } - - for _, dino := range dinosaurs { - converted := presenters.PresentDinosaur(&dino) - dinoList.Items = append(dinoList.Items, converted) - } - if listArgs.Fields != nil { - filteredItems, err := presenters.SliceFilter(listArgs.Fields, dinoList.Items) - if err != nil { - return nil, err - } - return filteredItems, nil - } - return dinoList, nil - }, - } - - handleList(w, r, cfg) -} - -func (h dinosaurHandler) Get(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ - Action: func() (interface{}, *errors.ServiceError) { - id := mux.Vars(r)["id"] - ctx := r.Context() - dinosaur, err := h.dinosaur.Get(ctx, id) - if err != nil { - return nil, err - } - - return presenters.PresentDinosaur(dinosaur), nil - }, - } - - handleGet(w, r, cfg) -} - -func (h dinosaurHandler) Delete(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ - Action: func() (interface{}, *errors.ServiceError) { - id := mux.Vars(r)["id"] - ctx := r.Context() - err := h.dinosaur.Delete(ctx, id) - if err != nil { - return nil, err - } - return nil, nil - }, - } - handleDelete(w, r, cfg, http.StatusNoContent) -} diff --git a/pkg/handlers/framework.go b/pkg/handlers/framework.go deleted file mode 100755 index fb3c8b9..0000000 --- a/pkg/handlers/framework.go +++ /dev/null @@ -1,128 +0,0 @@ -package handlers - -import ( - "context" - "encoding/json" - "io" - "net/http" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" -) - -// handlerConfig defines the common things each REST controller must do. -// The corresponding handle() func runs the basic handlerConfig. -// This is not meant to be an HTTP framework or anything larger than simple CRUD in handlers. -// -// MarshalInto is a pointer to the object to hold the unmarshaled JSON. -// Validate is a list of validation function that run in order, returning fast on the first error. -// Action is the specific logic a handler must take (e.g, find an object, save an object) -// ErrorHandler is the way errors are returned to the client -type handlerConfig struct { - MarshalInto interface{} - Validate []validate - Action httpAction - ErrorHandler errorHandlerFunc -} - -type validate func() *errors.ServiceError -type errorHandlerFunc func(ctx context.Context, w http.ResponseWriter, err *errors.ServiceError) -type httpAction func() (interface{}, *errors.ServiceError) - -func handleError(ctx context.Context, w http.ResponseWriter, err *errors.ServiceError) { - log := logger.NewOCMLogger(ctx) - operationID := logger.GetOperationID(ctx) - // If this is a 400 error, its the user's issue, log as info rather than error - if err.HttpCode >= 400 && err.HttpCode <= 499 { - log.Infof(err.Error()) - } else { - log.Error(err.Error()) - } - writeJSONResponse(w, err.HttpCode, err.AsOpenapiError(operationID)) -} - -func handle(w http.ResponseWriter, r *http.Request, cfg *handlerConfig, httpStatus int) { - if cfg.ErrorHandler == nil { - cfg.ErrorHandler = handleError - } - - bytes, err := io.ReadAll(r.Body) - if err != nil { - handleError(r.Context(), w, errors.MalformedRequest("Unable to read request body: %s", err)) - return - } - - err = json.Unmarshal(bytes, &cfg.MarshalInto) - if err != nil { - handleError(r.Context(), w, errors.MalformedRequest("Invalid request format: %s", err)) - return - } - - for _, v := range cfg.Validate { - err := v() - if err != nil { - cfg.ErrorHandler(r.Context(), w, err) - return - } - } - - result, serviceErr := cfg.Action() - - switch { - case serviceErr != nil: - cfg.ErrorHandler(r.Context(), w, serviceErr) - default: - writeJSONResponse(w, httpStatus, result) - } - -} - -func handleDelete(w http.ResponseWriter, r *http.Request, cfg *handlerConfig, httpStatus int) { - if cfg.ErrorHandler == nil { - cfg.ErrorHandler = handleError - } - for _, v := range cfg.Validate { - err := v() - if err != nil { - cfg.ErrorHandler(r.Context(), w, err) - return - } - } - - result, serviceErr := cfg.Action() - - switch { - case serviceErr != nil: - cfg.ErrorHandler(r.Context(), w, serviceErr) - default: - writeJSONResponse(w, httpStatus, result) - } - -} - -func handleGet(w http.ResponseWriter, r *http.Request, cfg *handlerConfig) { - if cfg.ErrorHandler == nil { - cfg.ErrorHandler = handleError - } - - result, serviceErr := cfg.Action() - switch { - case serviceErr == nil: - writeJSONResponse(w, http.StatusOK, result) - default: - cfg.ErrorHandler(r.Context(), w, serviceErr) - } -} - -func handleList(w http.ResponseWriter, r *http.Request, cfg *handlerConfig) { - if cfg.ErrorHandler == nil { - cfg.ErrorHandler = handleError - } - - results, serviceError := cfg.Action() - if serviceError != nil { - cfg.ErrorHandler(r.Context(), w, serviceError) - return - } - writeJSONResponse(w, http.StatusOK, results) -} diff --git a/pkg/handlers/framework_test.go b/pkg/handlers/framework_test.go deleted file mode 100755 index 8672fed..0000000 --- a/pkg/handlers/framework_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package handlers - -import "net/http" - -type mockResponseWriter struct { - written string - status int -} - -func (m *mockResponseWriter) Header() http.Header { - return map[string][]string{} -} -func (m *mockResponseWriter) Write(b []byte) (int, error) { - m.written = string(b) - return 0, nil -} -func (m *mockResponseWriter) WriteHeader(code int) { - m.status = code -} diff --git a/pkg/handlers/helpers.go b/pkg/handlers/helpers.go deleted file mode 100755 index 85ba934..0000000 --- a/pkg/handlers/helpers.go +++ /dev/null @@ -1,38 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - "reflect" -) - -func writeJSONResponse(w http.ResponseWriter, code int, payload interface{}) { - w.Header().Set("Content-Type", "application/json") - // By default, decide whether or not a cache is usable based on the matching of the JWT - // For example, this will keep caches from being used in the same browser if two users were to log in back to back - w.Header().Set("Vary", "Authorization") - - w.WriteHeader(code) - - if payload != nil { - response, _ := json.Marshal(payload) - _, _ = w.Write(response) - } -} - -// Prepare a 'list' of non-db-backed resources -func determineListRange(obj interface{}, page int, size int64) (list []interface{}, total int64) { - items := reflect.ValueOf(obj) - total = int64(items.Len()) - low := int64(page-1) * size - high := low + size - if low < 0 || low >= total || high >= total { - low = 0 - high = total - } - for i := low; i < high; i++ { - list = append(list, items.Index(int(i)).Interface()) - } - - return list, total -} diff --git a/pkg/handlers/metadata.go b/pkg/handlers/metadata.go deleted file mode 100755 index 89ebf56..0000000 --- a/pkg/handlers/metadata.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright (c) 2018 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package handlers - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/getsentry/sentry-go" - "github.com/golang/glog" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" -) - -type metadataHandler struct{} - -func NewMetadataHandler() *metadataHandler { - return &metadataHandler{} -} - -// Get sends API documentation response. -func (h metadataHandler) Get(w http.ResponseWriter, r *http.Request) { - // Set the content type: - w.Header().Set("Content-Type", "application/json") - - // Prepare the body: - body := api.Metadata{ - ID: "registry-credential-service", - Kind: "API", - HREF: r.URL.Path, - Version: api.Version, - BuildTime: api.BuildTime, - } - data, err := json.Marshal(body) - if err != nil { - api.SendPanic(w, r) - return - } - - // Send the response: - _, err = w.Write(data) - if err != nil { - err = fmt.Errorf("can't send response body for request '%s'", r.URL.Path) - glog.Error(err) - sentry.CaptureException(err) - return - } -} diff --git a/pkg/handlers/openapi-ui.html b/pkg/handlers/openapi-ui.html deleted file mode 100755 index 2ff1e27..0000000 --- a/pkg/handlers/openapi-ui.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - SwaggerUI - - - -
- - - - - diff --git a/pkg/handlers/openapi.go b/pkg/handlers/openapi.go deleted file mode 100755 index 071a830..0000000 --- a/pkg/handlers/openapi.go +++ /dev/null @@ -1,68 +0,0 @@ -package handlers - -import ( - "embed" - "io/fs" - "net/http" - - "github.com/ghodss/yaml" - "github.com/golang/glog" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -//go:embed openapi-ui.html -var openapiui embed.FS - -type openAPIHandler struct { - openAPIDefinitions []byte - uiContent []byte -} - -func NewOpenAPIHandler() (*openAPIHandler, error) { - // Load the fully resolved OpenAPI spec from embedded filesystem - resolvedData, err := api.GetOpenAPISpec() - if err != nil { - return nil, errors.GeneralError( - "can't load OpenAPI specification from embedded file: %v", - err, - ) - } - - // Convert YAML to JSON - data, err := yaml.YAMLToJSON(resolvedData) - if err != nil { - return nil, errors.GeneralError( - "can't convert OpenAPI specification from YAML to JSON: %v", - err, - ) - } - glog.Info("Loaded fully resolved OpenAPI specification from embedded pkg/api/openapi/api/openapi.yaml") - - // Load the OpenAPI UI HTML content - uiContent, err := fs.ReadFile(openapiui, "openapi-ui.html") - if err != nil { - return nil, errors.GeneralError( - "can't load OpenAPI UI HTML from embedded file: %v", - err, - ) - } - glog.Info("Loaded OpenAPI UI HTML from embedded file") - - return &openAPIHandler{ - openAPIDefinitions: data, - uiContent: uiContent, - }, nil -} - -func (h *openAPIHandler) GetOpenAPI(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = w.Write(h.openAPIDefinitions) -} - -func (h *openAPIHandler) GetOpenAPIUI(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html") - w.WriteHeader(http.StatusOK) - _, _ = w.Write(h.uiContent) -} diff --git a/pkg/handlers/prometheus_metrics.go b/pkg/handlers/prometheus_metrics.go deleted file mode 100755 index 496a3cd..0000000 --- a/pkg/handlers/prometheus_metrics.go +++ /dev/null @@ -1,23 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -type prometheusMetricsHandler struct { -} - -// NewPrometheusMetricsHandler adds custom metrics and proxy to prometheus handler -func NewPrometheusMetricsHandler() *prometheusMetricsHandler { - return &prometheusMetricsHandler{} -} - -func (h *prometheusMetricsHandler) Handler() http.Handler { - handler := promhttp.Handler() - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - handler.ServeHTTP(w, r) - }) -} diff --git a/pkg/handlers/rest.go b/pkg/handlers/rest.go deleted file mode 100755 index 63c0556..0000000 --- a/pkg/handlers/rest.go +++ /dev/null @@ -1,11 +0,0 @@ -package handlers - -import "net/http" - -type RestHandler interface { - List(w http.ResponseWriter, r *http.Request) - Get(w http.ResponseWriter, r *http.Request) - Create(w http.ResponseWriter, r *http.Request) - Patch(w http.ResponseWriter, r *http.Request) - Delete(w http.ResponseWriter, r *http.Request) -} diff --git a/pkg/handlers/validation.go b/pkg/handlers/validation.go deleted file mode 100755 index eec0447..0000000 --- a/pkg/handlers/validation.go +++ /dev/null @@ -1,68 +0,0 @@ -package handlers - -import ( - "reflect" - "strings" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -func validateNotEmpty(i interface{}, fieldName string, field string) validate { - return func() *errors.ServiceError { - value := reflect.ValueOf(i).Elem().FieldByName(fieldName) - if value.Kind() == reflect.Ptr { - if value.IsNil() { - return errors.Validation("%s is required", field) - } - value = value.Elem() - } - if len(value.String()) == 0 { - return errors.Validation("%s is required", field) - } - return nil - } -} - -func validateEmpty(i interface{}, fieldName string, field string) validate { - return func() *errors.ServiceError { - value := reflect.ValueOf(i).Elem().FieldByName(fieldName) - if value.Kind() == reflect.Ptr { - if value.IsNil() { - return nil - } - value = value.Elem() - } - if len(value.String()) != 0 { - return errors.Validation("%s must be empty", field) - } - return nil - } -} - -// Note that because this uses strings.EqualFold, it is case-insensitive -func validateInclusionIn(value *string, list []string, category *string) validate { - return func() *errors.ServiceError { - for _, item := range list { - if strings.EqualFold(*value, item) { - return nil - } - } - if category == nil { - category = &[]string{"value"}[0] - } - return errors.Validation("%s is not a valid %s", *value, *category) - } -} - -func validateDinosaurPatch(patch *openapi.DinosaurPatchRequest) validate { - return func() *errors.ServiceError { - if patch.Species == nil { - return errors.Validation("species cannot be nil") - } - if len(*patch.Species) == 0 { - return errors.Validation("species cannot be empty") - } - return nil - } -} diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go deleted file mode 100755 index be1cc5f..0000000 --- a/pkg/logger/logger.go +++ /dev/null @@ -1,147 +0,0 @@ -package logger - -import ( - "context" - "fmt" - "strings" - - "github.com/getsentry/sentry-go" - "github.com/golang/glog" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/util" -) - -type OCMLogger interface { - V(level int32) OCMLogger - Infof(format string, args ...interface{}) - Extra(key string, value interface{}) OCMLogger - Info(message string) - Warning(message string) - Error(message string) - Fatal(message string) -} - -var _ OCMLogger = &logger{} - -type extra map[string]interface{} - -type logger struct { - context context.Context - level int32 - accountID string - // TODO username is unused, should we be logging it? Could be pii - username string - sentryHub *sentry.Hub - extra extra -} - -// NewOCMLogger creates a new logger instance with a default verbosity of 1 -func NewOCMLogger(ctx context.Context) OCMLogger { - logger := &logger{ - context: ctx, - level: 1, - extra: make(extra), - accountID: util.GetAccountIDFromContext(ctx), - sentryHub: sentry.GetHubFromContext(ctx), - } - return logger -} - -func (l *logger) prepareLogPrefix(message string, extra extra) string { - prefix := " " - - if txid, ok := l.context.Value("txid").(int64); ok { - prefix = fmt.Sprintf("[tx_id=%d]%s", txid, prefix) - } - - if l.accountID != "" { - prefix = fmt.Sprintf("[accountID=%s]%s", l.accountID, prefix) - } - - if opid, ok := l.context.Value(OpIDKey).(string); ok { - prefix = fmt.Sprintf("[opid=%s]%s", opid, prefix) - } - - var args []string - for k, v := range extra { - args = append(args, fmt.Sprintf("%s=%v", k, v)) - } - - return fmt.Sprintf("%s %s %s", prefix, message, strings.Join(args, " ")) -} - -func (l *logger) prepareLogPrefixf(format string, args ...interface{}) string { - orig := fmt.Sprintf(format, args...) - prefix := " " - - if txid, ok := l.context.Value("txid").(int64); ok { - prefix = fmt.Sprintf("[tx_id=%d]%s", txid, prefix) - } - - if l.accountID != "" { - prefix = fmt.Sprintf("[accountID=%s]%s", l.accountID, prefix) - } - - if opid, ok := l.context.Value(OpIDKey).(string); ok { - prefix = fmt.Sprintf("[opid=%s]%s", opid, prefix) - } - - return fmt.Sprintf("%s%s", prefix, orig) -} - -func (l *logger) V(level int32) OCMLogger { - return &logger{ - context: l.context, - accountID: l.accountID, - username: l.username, - level: level, - } -} - -// Infof doesn't trigger Sentry error -func (l *logger) Infof(format string, args ...interface{}) { - prefixed := l.prepareLogPrefixf(format, args...) - glog.V(glog.Level(l.level)).Infof("%s", prefixed) -} - -func (l *logger) Extra(key string, value interface{}) OCMLogger { - l.extra[key] = value - return l -} - -func (l *logger) Info(message string) { - l.log(message, sentry.LevelInfo, glog.V(glog.Level(l.level)).Infoln) -} - -func (l *logger) Warning(message string) { - l.log(message, sentry.LevelWarning, glog.Warningln) -} - -func (l *logger) Error(message string) { - l.log(message, sentry.LevelError, glog.Errorln) -} - -func (l *logger) Fatal(message string) { - l.log(message, sentry.LevelFatal, glog.Fatalln) -} - -func (l *logger) log(message string, level sentry.Level, glogFunc func(args ...interface{})) { - prefixed := l.prepareLogPrefix(message, l.extra) - glogFunc(prefixed) - if level != sentry.LevelInfo && level != sentry.LevelWarning { - l.captureSentryEvent(level, message) - } -} - -func (l *logger) captureSentryEvent(level sentry.Level, message string) { - event := sentry.NewEvent() - event.Level = level - event.Message = message - event.Extra = l.extra - captureFunc := sentry.CaptureEvent - if l.sentryHub == nil { - sentry.CaptureException(fmt.Errorf("sentry hub not present in logger")) - } else { - captureFunc = l.sentryHub.CaptureEvent - } - captureFunc(event) -} diff --git a/pkg/logger/operationid_middleware.go b/pkg/logger/operationid_middleware.go deleted file mode 100755 index 4ff2237..0000000 --- a/pkg/logger/operationid_middleware.go +++ /dev/null @@ -1,51 +0,0 @@ -package logger - -import ( - "context" - "net/http" - - "github.com/getsentry/sentry-go" - "github.com/segmentio/ksuid" -) - -type OperationIDKey string - -const OpIDKey OperationIDKey = "opID" -const OpIDHeader OperationIDKey = "X-Operation-ID" - -// OperationIDMiddleware Middleware wraps the given HTTP handler so that the details of the request are sent to the log. -func OperationIDMiddleware(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := WithOpID(r.Context()) - - opID, ok := ctx.Value(OpIDKey).(string) - if ok && len(opID) > 0 { - w.Header().Set(string(OpIDHeader), opID) - } - - // Add operation ID to sentry context - if hub := sentry.GetHubFromContext(ctx); hub != nil { - hub.ConfigureScope(func(scope *sentry.Scope) { - scope.SetTag("operation_id", opID) - }) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -func WithOpID(ctx context.Context) context.Context { - if ctx.Value(OpIDKey) != nil { - return ctx - } - opID := ksuid.New().String() - return context.WithValue(ctx, OpIDKey, opID) -} - -// GetOperationID get operationID of the context -func GetOperationID(ctx context.Context) string { - if opID, ok := ctx.Value(OpIDKey).(string); ok { - return opID - } - return "" -} diff --git a/pkg/services/dinosaurs.go b/pkg/services/dinosaurs.go deleted file mode 100755 index debab96..0000000 --- a/pkg/services/dinosaurs.go +++ /dev/null @@ -1,192 +0,0 @@ -package services - -import ( - "context" - "time" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -// These flag will only be used in integration test to prove that the advisory lock works -var ( - DisableAdvisoryLock = false - UseBlockingAdvisoryLock = true -) - -type DinosaurService interface { - Get(ctx context.Context, id string) (*api.Dinosaur, *errors.ServiceError) - Create(ctx context.Context, dinosaur *api.Dinosaur) (*api.Dinosaur, *errors.ServiceError) - Replace(ctx context.Context, dinosaur *api.Dinosaur) (*api.Dinosaur, *errors.ServiceError) - Delete(ctx context.Context, id string) *errors.ServiceError - All(ctx context.Context) (api.DinosaurList, *errors.ServiceError) - - FindBySpecies(ctx context.Context, species string) (api.DinosaurList, *errors.ServiceError) - FindByIDs(ctx context.Context, ids []string) (api.DinosaurList, *errors.ServiceError) - - // idempotent functions for the control plane, but can also be called synchronously by any actor - OnUpsert(ctx context.Context, id string) error - OnDelete(ctx context.Context, id string) error -} - -func NewDinosaurService(lockFactory db.LockFactory, dinosaurDao dao.DinosaurDao, events EventService) DinosaurService { - return &sqlDinosaurService{ - lockFactory: lockFactory, - dinosaurDao: dinosaurDao, - events: events, - } -} - -var _ DinosaurService = &sqlDinosaurService{} - -type sqlDinosaurService struct { - lockFactory db.LockFactory - dinosaurDao dao.DinosaurDao - events EventService -} - -func (s *sqlDinosaurService) OnUpsert(ctx context.Context, id string) error { - logger := logger.NewOCMLogger(ctx) - - dinosaur, err := s.dinosaurDao.Get(ctx, id) - if err != nil { - return err - } - - logger.Infof("Do idempotent somethings with this dinosaur: %s", dinosaur.ID) - - return nil -} - -func (s *sqlDinosaurService) OnDelete(ctx context.Context, id string) error { - logger := logger.NewOCMLogger(ctx) - logger.Infof("This dino didn't make it to the asteroid: %s", id) - return nil -} - -func (s *sqlDinosaurService) Get(ctx context.Context, id string) (*api.Dinosaur, *errors.ServiceError) { - dinosaur, err := s.dinosaurDao.Get(ctx, id) - if err != nil { - return nil, handleGetError("Dinosaur", "id", id, err) - } - return dinosaur, nil -} - -func (s *sqlDinosaurService) Create(ctx context.Context, dinosaur *api.Dinosaur) (*api.Dinosaur, *errors.ServiceError) { - dinosaur, err := s.dinosaurDao.Create(ctx, dinosaur) - if err != nil { - return nil, handleCreateError("Dinosaur", err) - } - - _, eErr := s.events.Create(ctx, &api.Event{ - Source: "Dinosaurs", - SourceID: dinosaur.ID, - EventType: api.CreateEventType, - }) - if eErr != nil { - return nil, handleCreateError("Dinosaur", err) - } - - return dinosaur, nil -} - -func (s *sqlDinosaurService) Replace(ctx context.Context, dinosaur *api.Dinosaur) (*api.Dinosaur, *errors.ServiceError) { - if !DisableAdvisoryLock { - // Updates the dinosaur species only when its species changes. - // If there are multiple requests at the same time, it will cause the race conditions among these - // requests (read–modify–write), the advisory lock is used here to prevent the race conditions. - if UseBlockingAdvisoryLock { - lockOwnerID, err := s.lockFactory.NewAdvisoryLock(ctx, dinosaur.ID, db.Dinosaurs) - if err != nil { - return nil, errors.DatabaseAdvisoryLock(err) - } - defer s.lockFactory.Unlock(ctx, lockOwnerID) - - } else { - lockOwnerID, locked, err := s.lockFactory.NewNonBlockingLock(ctx, dinosaur.ID, db.Dinosaurs) - if err != nil { - return nil, errors.DatabaseAdvisoryLock(err) - } - if !locked { - return nil, handleCreateError("Dinosaur", errors.New(errors.ErrorConflict, "row locked")) - } - defer s.lockFactory.Unlock(ctx, lockOwnerID) - } - } - - found, err := s.dinosaurDao.Get(ctx, dinosaur.ID) - if err != nil { - return nil, handleGetError("Dinosaur", "id", dinosaur.ID, err) - } - - // this is for integration tests that use Advisory Locks - if dinosaur.Species == "AdvisoryLockosaurus" { - time.Sleep(1 * time.Second) - } - - // New species is no change, the update action is not needed. - if found.Species == dinosaur.Species { - return found, nil - } - - found.Species = dinosaur.Species - updated, err := s.dinosaurDao.Replace(ctx, found) - if err != nil { - return nil, handleUpdateError("Dinosaur", err) - } - - _, eErr := s.events.Create(ctx, &api.Event{ - Source: "Dinosaurs", - SourceID: updated.ID, - EventType: api.UpdateEventType, - }) - if eErr != nil { - return nil, handleUpdateError("Dinosaur", err) - } - return updated, nil -} - -func (s *sqlDinosaurService) Delete(ctx context.Context, id string) *errors.ServiceError { - if err := s.dinosaurDao.Delete(ctx, id); err != nil { - return handleDeleteError("Dinosaur", errors.GeneralError("Unable to delete dinosaur: %s", err)) - } - - _, err := s.events.Create(ctx, &api.Event{ - Source: "Dinosaurs", - SourceID: id, - EventType: api.DeleteEventType, - }) - if err != nil { - return handleDeleteError("Dinosaur", err) - } - - return nil -} - -func (s *sqlDinosaurService) FindByIDs(ctx context.Context, ids []string) (api.DinosaurList, *errors.ServiceError) { - dinosaurs, err := s.dinosaurDao.FindByIDs(ctx, ids) - if err != nil { - return nil, errors.GeneralError("Unable to get all dinosaurs: %s", err) - } - return dinosaurs, nil -} - -func (s *sqlDinosaurService) FindBySpecies(ctx context.Context, species string) (api.DinosaurList, *errors.ServiceError) { - dinosaurs, err := s.dinosaurDao.FindBySpecies(ctx, species) - if err != nil { - return nil, handleGetError("Dinosaur", "species", species, err) - } - return dinosaurs, nil -} - -func (s *sqlDinosaurService) All(ctx context.Context) (api.DinosaurList, *errors.ServiceError) { - dinosaurs, err := s.dinosaurDao.All(ctx) - if err != nil { - return nil, errors.GeneralError("Unable to get all dinosaurs: %s", err) - } - return dinosaurs, nil -} diff --git a/pkg/services/dinosaurs_test.go b/pkg/services/dinosaurs_test.go deleted file mode 100755 index 01b29d6..0000000 --- a/pkg/services/dinosaurs_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package services - -import ( - "context" - "testing" - - gm "github.com/onsi/gomega" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao/mocks" - dbmocks "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/mocks" -) - -func TestDinosaurFindBySpecies(t *testing.T) { - gm.RegisterTestingT(t) - - dinoDAO := mocks.NewDinosaurDao() - events := NewEventService(mocks.NewEventDao()) - dinoService := NewDinosaurService(dbmocks.NewMockAdvisoryLockFactory(), dinoDAO, events) - - const Fukuisaurus = "Fukuisaurus" - const Seismosaurus = "Seismosaurus" - const Breviceratops = "Breviceratops" - - dinos := api.DinosaurList{ - &api.Dinosaur{Species: Fukuisaurus}, - &api.Dinosaur{Species: Fukuisaurus}, - &api.Dinosaur{Species: Fukuisaurus}, - &api.Dinosaur{Species: Seismosaurus}, - &api.Dinosaur{Species: Seismosaurus}, - &api.Dinosaur{Species: Breviceratops}, - } - for _, dino := range dinos { - _, err := dinoService.Create(context.Background(), dino) - gm.Expect(err).To(gm.BeNil()) - } - fukuisaurus, err := dinoService.FindBySpecies(context.Background(), Fukuisaurus) - gm.Expect(err).To(gm.BeNil()) - gm.Expect(len(fukuisaurus)).To(gm.Equal(3)) - - seismosaurus, err := dinoService.FindBySpecies(context.Background(), Seismosaurus) - gm.Expect(err).To(gm.BeNil()) - gm.Expect(len(seismosaurus)).To(gm.Equal(2)) - - breviceratops, err := dinoService.FindBySpecies(context.Background(), Breviceratops) - gm.Expect(err).To(gm.BeNil()) - gm.Expect(len(breviceratops)).To(gm.Equal(1)) -} diff --git a/pkg/services/event.go b/pkg/services/event.go deleted file mode 100755 index 5d53ab6..0000000 --- a/pkg/services/event.go +++ /dev/null @@ -1,78 +0,0 @@ -package services - -import ( - "context" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -type EventService interface { - Get(ctx context.Context, id string) (*api.Event, *errors.ServiceError) - Create(ctx context.Context, event *api.Event) (*api.Event, *errors.ServiceError) - Replace(ctx context.Context, event *api.Event) (*api.Event, *errors.ServiceError) - Delete(ctx context.Context, id string) *errors.ServiceError - All(ctx context.Context) (api.EventList, *errors.ServiceError) - - FindByIDs(ctx context.Context, ids []string) (api.EventList, *errors.ServiceError) -} - -func NewEventService(eventDao dao.EventDao) EventService { - return &sqlEventService{ - eventDao: eventDao, - } -} - -var _ EventService = &sqlEventService{} - -type sqlEventService struct { - eventDao dao.EventDao -} - -func (s *sqlEventService) Get(ctx context.Context, id string) (*api.Event, *errors.ServiceError) { - event, err := s.eventDao.Get(ctx, id) - if err != nil { - return nil, handleGetError("Event", "id", id, err) - } - return event, nil -} - -func (s *sqlEventService) Create(ctx context.Context, event *api.Event) (*api.Event, *errors.ServiceError) { - event, err := s.eventDao.Create(ctx, event) - if err != nil { - return nil, handleCreateError("Event", err) - } - return event, nil -} - -func (s *sqlEventService) Replace(ctx context.Context, event *api.Event) (*api.Event, *errors.ServiceError) { - event, err := s.eventDao.Replace(ctx, event) - if err != nil { - return nil, handleUpdateError("Event", err) - } - return event, nil -} - -func (s *sqlEventService) Delete(ctx context.Context, id string) *errors.ServiceError { - if err := s.eventDao.Delete(ctx, id); err != nil { - return handleDeleteError("Event", errors.GeneralError("Unable to delete event: %s", err)) - } - return nil -} - -func (s *sqlEventService) FindByIDs(ctx context.Context, ids []string) (api.EventList, *errors.ServiceError) { - events, err := s.eventDao.FindByIDs(ctx, ids) - if err != nil { - return nil, errors.GeneralError("Unable to get all events: %s", err) - } - return events, nil -} - -func (s *sqlEventService) All(ctx context.Context) (api.EventList, *errors.ServiceError) { - events, err := s.eventDao.All(ctx) - if err != nil { - return nil, errors.GeneralError("Unable to get all events: %s", err) - } - return events, nil -} diff --git a/pkg/services/generic.go b/pkg/services/generic.go deleted file mode 100755 index afde163..0000000 --- a/pkg/services/generic.go +++ /dev/null @@ -1,346 +0,0 @@ -package services - -import ( - "context" - e "errors" - "fmt" - "reflect" - "strings" - - "gorm.io/gorm" - - "github.com/Masterminds/squirrel" - "github.com/yaacov/tree-search-language/pkg/tsl" - "github.com/yaacov/tree-search-language/pkg/walkers/ident" - sqlFilter "github.com/yaacov/tree-search-language/pkg/walkers/sql" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" -) - -type GenericService interface { - List(ctx context.Context, username string, args *ListArguments, resourceList interface{}) (*api.PagingMeta, *errors.ServiceError) -} - -func NewGenericService(genericDao dao.GenericDao) GenericService { - return &sqlGenericService{genericDao: genericDao} -} - -var _ GenericService = &sqlGenericService{} - -type sqlGenericService struct { - genericDao dao.GenericDao -} - -var ( - SearchDisallowedFields = map[string]map[string]string{} - allFieldsAllowed = map[string]string{} -) - -// wrap all needed pieces for the LIST funciton -type listContext struct { - ctx context.Context - args *ListArguments - username string - pagingMeta *api.PagingMeta - ulog *logger.OCMLogger - resourceList interface{} - disallowedFields *map[string]string - resourceType string - joins map[string]dao.TableRelation - groupBy []string - set map[string]bool -} - -func (s *sqlGenericService) newListContext(ctx context.Context, username string, args *ListArguments, resourceList interface{}) (*listContext, interface{}, *errors.ServiceError) { - log := logger.NewOCMLogger(ctx) - resourceModel := reflect.TypeOf(resourceList).Elem().Elem() - resourceTypeStr := resourceModel.Name() - if resourceTypeStr == "" { - return nil, nil, errors.GeneralError("Could not determine resource type") - } - disallowedFields := SearchDisallowedFields[resourceTypeStr] - if disallowedFields == nil { - disallowedFields = allFieldsAllowed - } - args.Search = strings.Trim(args.Search, " ") - return &listContext{ - ctx: ctx, - args: args, - username: username, - pagingMeta: &api.PagingMeta{Page: args.Page}, - ulog: &log, - resourceList: resourceList, - disallowedFields: &disallowedFields, - resourceType: resourceTypeStr, - }, reflect.New(resourceModel).Interface(), nil -} - -// List resourceList must be a pointer to a slice of database resource objects -func (s *sqlGenericService) List(ctx context.Context, username string, args *ListArguments, resourceList interface{}) (*api.PagingMeta, *errors.ServiceError) { - listCtx, model, err := s.newListContext(ctx, username, args, resourceList) - if err != nil { - return nil, err - } - - // the ordering for the sub functions matters. - builders := []listBuilder{ - // build SQL to load related resource. for now, it delegates to gorm.preload. - s.buildPreload, - - // add "ORDER BY" - s.buildOrderBy, - - // translate "search" into "WHERE"(s), and "JOIN"(s) if related resource is searched. - s.buildSearch, - - // TODO: add any custom builder functions - } - - d := s.genericDao.GetInstanceDao(ctx, model) - - // run all the "builders". they cumulatively add constructs to gorm by the context. - // it stops when a builder function raises error or signals finished. - var finished bool - for _, builderFn := range builders { - if finished, err = builderFn(listCtx, &d); err != nil { - return nil, err - } - if finished { - if err = s.loadList(listCtx, &d); err != nil { - return nil, err - } - break - } - } - return listCtx.pagingMeta, nil -} - -/*** Define all sub functions in the type of listBuilder ***/ -type listBuilder func(*listContext, *dao.GenericDao) (finished bool, err *errors.ServiceError) - -func (s *sqlGenericService) buildPreload(listCtx *listContext, d *dao.GenericDao) (bool, *errors.ServiceError) { - listCtx.set = make(map[string]bool) - - for _, preload := range listCtx.args.Preloads { - listCtx.set[preload] = true - } - // preload each table only once; struct{} doesn't occupy any additional space - for _, preload := range listCtx.args.Preloads { - (*d).Preload(preload) - } - return false, nil -} - -func (s *sqlGenericService) buildOrderBy(listCtx *listContext, d *dao.GenericDao) (bool, *errors.ServiceError) { - if len(listCtx.args.OrderBy) != 0 { - orderByArgs, serviceErr := db.ArgsToOrderBy(listCtx.args.OrderBy, *listCtx.disallowedFields) - if serviceErr != nil { - return false, serviceErr - } - for _, orderByArg := range orderByArgs { - (*d).OrderBy(orderByArg) - } - } - return false, nil -} - -func (s *sqlGenericService) buildSearchValues(listCtx *listContext, d *dao.GenericDao) (string, []any, *errors.ServiceError) { - if listCtx.args.Search == "" { - s.addJoins(listCtx, d) - return "", nil, nil - } - - // create the TSL tree - tslTree, err := tsl.ParseTSL(listCtx.args.Search) - if err != nil { - return "", nil, errors.BadRequest("Failed to parse search query: %s", listCtx.args.Search) - } - // find all related tables - tslTree, serviceErr := s.treeWalkForRelatedTables(listCtx, tslTree, d) - if serviceErr != nil { - return "", nil, serviceErr - } - // prepend table names to prevent "ambiguous" errors - tslTree, serviceErr = s.treeWalkForAddingTableName(listCtx, tslTree, d) - if serviceErr != nil { - return "", nil, serviceErr - } - // convert to sqlizer - _, sqlizer, serviceErr := s.treeWalkForSqlizer(listCtx, tslTree) - if serviceErr != nil { - return "", nil, serviceErr - } - - s.addJoins(listCtx, d) - - // parse the search string to SQL WHERE - sql, values, err := sqlizer.ToSql() - if err != nil { - return "", nil, errors.GeneralError("%s", err.Error()) - } - return sql, values, nil -} - -func (s *sqlGenericService) buildSearch(listCtx *listContext, d *dao.GenericDao) (bool, *errors.ServiceError) { - sql, values, err := s.buildSearchValues(listCtx, d) - if err != nil { - return false, err - } - (*d).Where(dao.NewWhere(sql, values)) - return true, nil -} - -// JOIN the tables that appear in the search string -func (s *sqlGenericService) addJoins(listCtx *listContext, d *dao.GenericDao) { - for _, r := range listCtx.joins { - if _, ok := listCtx.set[r.ForeignTableName]; ok { - // skip already included preloads - continue - } - sql := fmt.Sprintf( - "LEFT JOIN %s ON %s.%s = %s.%s AND %s.deleted_at IS NULL", - r.ForeignTableName, r.ForeignTableName, r.ForeignColumnName, r.TableName, r.ColumnName, r.ForeignTableName) - (*d).Joins(sql) - - listCtx.groupBy = append(listCtx.groupBy, r.ForeignTableName+".id") - listCtx.set[r.ForeignTableName] = true - } - if len(listCtx.joins) > 0 { - // Add base relation - listCtx.groupBy = append(listCtx.groupBy, (*d).GetTableName()+".id") - (*d).Group(strings.Join(listCtx.groupBy, ",")) - } - - // Reset list of joins and group by's - listCtx.joins = map[string]dao.TableRelation{} -} - -func (s *sqlGenericService) loadList(listCtx *listContext, d *dao.GenericDao) *errors.ServiceError { - args := listCtx.args - ulog := *listCtx.ulog - - (*d).Count(listCtx.resourceList, &listCtx.pagingMeta.Total) - - // Set resourceList to be an empty slice with zero capacity. Real space will be allocated by g2.Find() - if err := zeroSlice(listCtx.resourceList, 0); err != nil { - return err - } - - switch { - case args.Size > MaxListSize: - ulog.Warning("A query with a size greater than the maximum was requested.") - case args.Size < 0: - ulog.Warning("A query with an unbound size was requested.") - case args.Size == 0: - // This early return is not only performant, but also necessary. - // gorm does not support Limit(0) any longer. - ulog.Infof("A query with 0 size requested, returning early without collecting any resources from database") - return nil - } - - // NOTE: Limit no longer supports '0' size and will cause issues. There is an early return, do not remove it. - // https://github.com/go-gorm/gorm/blob/master/clause/limit.go#L18-L21 - if err := (*d).Fetch((args.Page-1)*int(args.Size), int(args.Size), listCtx.resourceList); err != nil { - if e.Is(err, gorm.ErrRecordNotFound) { - listCtx.pagingMeta.Size = 0 - } else { - return errors.GeneralError("Unable to list resources: %s", err) - } - } - listCtx.pagingMeta.Size = int64(reflect.ValueOf(listCtx.resourceList).Elem().Len()) - - return nil -} - -// Allocate a slice with size 'cap' of the type i -func zeroSlice(i interface{}, cap int64) *errors.ServiceError { - v := reflect.ValueOf(i) - if v.Kind() != reflect.Ptr { - return errors.GeneralError("A non-pointer to a list of resources: %v", v.Type()) - } - // get the value that the pointer v points to. - v = v.Elem() - if v.Kind() != reflect.Slice { - return errors.GeneralError("A non-slice list of resources") - } - v.Set(reflect.MakeSlice(v.Type(), 0, int(cap))) - return nil -} - -// walk the TSL tree looking for fields like, e.g., creator.username, and then: -// (1) look up the related table by its 1st part - creator -// (2) replace it by table name - creator.username -> accounts.username -func (s *sqlGenericService) treeWalkForRelatedTables(listCtx *listContext, tslTree tsl.Node, genericDao *dao.GenericDao) (tsl.Node, *errors.ServiceError) { - resourceTable := (*genericDao).GetTableName() - if listCtx.joins == nil { - listCtx.joins = map[string]dao.TableRelation{} - } - walkFn := func(field string) (string, error) { - fieldParts := strings.Split(field, ".") - if len(fieldParts) > 1 && fieldParts[0] != resourceTable { - fieldName := fieldParts[0] - _, exists := listCtx.joins[fieldName] - if !exists { - if relation, ok := (*genericDao).GetTableRelation(fieldName); ok { - listCtx.joins[fieldName] = relation - } else { - return field, fmt.Errorf("%s is not a related resource of %s", fieldName, listCtx.resourceType) - } - } - //replace by table name - fieldParts[0] = listCtx.joins[fieldName].ForeignTableName - return strings.Join(fieldParts, "."), nil - } - return field, nil - } - - tslTree, err := ident.Walk(tslTree, walkFn) - if err != nil { - return tslTree, errors.BadRequest("%s", err.Error()) - } - - return tslTree, nil -} - -// prepend table name to these "free" identifiers since they could cause "ambiguous" errors -func (s *sqlGenericService) treeWalkForAddingTableName(listCtx *listContext, tslTree tsl.Node, dao *dao.GenericDao) (tsl.Node, *errors.ServiceError) { - resourceTable := (*dao).GetTableName() - - walkFn := func(field string) (string, error) { - fieldParts := strings.Split(field, ".") - if len(fieldParts) == 1 { - if strings.Contains(field, "->") { - return field, nil - } - return fmt.Sprintf("%s.%s", resourceTable, field), nil - } - return field, nil - } - - tslTree, err := ident.Walk(tslTree, walkFn) - if err != nil { - return tslTree, errors.BadRequest("%s", err.Error()) - } - - return tslTree, nil -} - -func (s *sqlGenericService) treeWalkForSqlizer(listCtx *listContext, tslTree tsl.Node) (tsl.Node, squirrel.Sqlizer, *errors.ServiceError) { - // Check field names in tree - tslTree, serviceErr := db.FieldNameWalk(tslTree, *listCtx.disallowedFields) - if serviceErr != nil { - return tslTree, nil, serviceErr - } - - // Convert the search tree into SQL [Squirrel] filter - sqlizer, err := sqlFilter.Walk(tslTree) - if err != nil { - return tslTree, nil, errors.BadRequest("%s", err.Error()) - } - - return tslTree, sqlizer, nil -} diff --git a/pkg/services/generic_test.go b/pkg/services/generic_test.go deleted file mode 100755 index 47ebf43..0000000 --- a/pkg/services/generic_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package services - -import ( - "context" - "testing" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - dbmocks "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db/mocks" - - "github.com/onsi/gomega/types" - "github.com/yaacov/tree-search-language/pkg/tsl" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" - - . "github.com/onsi/gomega" -) - -func TestSQLTranslation(t *testing.T) { - RegisterTestingT(t) - var dbFactory db.SessionFactory = dbmocks.NewMockSessionFactory() - defer dbFactory.Close() - - g := dao.NewGenericDao(&dbFactory) - genericService := sqlGenericService{genericDao: g} - - // ill-formatted search or disallowed fields should be rejected - tests := []map[string]interface{}{ - { - "search": "garbage", - "error": "registry-credential-service-21: Failed to parse search query: garbage", - }, - { - "search": "id in ('123')", - "error": "registry-credential-service-21: dinosaurs.id is not a valid field name", - }, - } - for _, test := range tests { - var list []api.Dinosaur - search := test["search"].(string) - errorMsg := test["error"].(string) - listCtx, model, serviceErr := genericService.newListContext(context.Background(), "", &ListArguments{Search: search}, &list) - Expect(serviceErr).ToNot(HaveOccurred()) - d := g.GetInstanceDao(context.Background(), model) - (*listCtx.disallowedFields)["id"] = "id" - _, serviceErr = genericService.buildSearch(listCtx, &d) - Expect(serviceErr).To(HaveOccurred()) - Expect(serviceErr.Code).To(Equal(errors.ErrorBadRequest)) - Expect(serviceErr.Error()).To(Equal(errorMsg)) - } - - // tests for sql parsing - tests = []map[string]interface{}{ - { - "search": "username in ('ooo.openshift')", - "sql": "username IN (?)", - "values": ConsistOf("ooo.openshift"), - }, - } - for _, test := range tests { - var list []api.Dinosaur - search := test["search"].(string) - sqlReal := test["sql"].(string) - valuesReal := test["values"].(types.GomegaMatcher) - listCtx, _, serviceErr := genericService.newListContext(context.Background(), "", &ListArguments{Search: search}, &list) - Expect(serviceErr).ToNot(HaveOccurred()) - tslTree, err := tsl.ParseTSL(search) - Expect(err).ToNot(HaveOccurred()) - _, sqlizer, serviceErr := genericService.treeWalkForSqlizer(listCtx, tslTree) - Expect(serviceErr).ToNot(HaveOccurred()) - sql, values, err := sqlizer.ToSql() - Expect(err).ToNot(HaveOccurred()) - Expect(sql).To(Equal(sqlReal)) - Expect(values).To(valuesReal) - } -} diff --git a/pkg/services/types.go b/pkg/services/types.go deleted file mode 100755 index f3e77dd..0000000 --- a/pkg/services/types.go +++ /dev/null @@ -1,67 +0,0 @@ -package services - -import ( - "net/url" - "strconv" - "strings" -) - -// ListArguments are arguments relevant for listing objects. -// This struct is common to all service List funcs in this package -type ListArguments struct { - Page int - Size int64 - Preloads []string - Search string - OrderBy []string - Fields []string -} - -// ~65500 is the maximum number of parameters that can be provided to a postgres WHERE IN clause -// Use it as a sane max -const MaxListSize = 65500 - -// NewListArguments Create ListArguments from url query parameters with sane defaults -func NewListArguments(params url.Values) *ListArguments { - listArgs := &ListArguments{ - Page: 1, - Size: 100, - Search: "", - } - if v := strings.Trim(params.Get("page"), " "); v != "" { - listArgs.Page, _ = strconv.Atoi(v) - } - if v := strings.Trim(params.Get("size"), " "); v != "" { - listArgs.Size, _ = strconv.ParseInt(v, 10, 0) - } - if listArgs.Size > MaxListSize || listArgs.Size < 0 { - // MaxListSize is the maximum number of *parameters* that can be provided to a postgres WHERE IN clause - // Use it as a sane max - listArgs.Size = MaxListSize - } - if v := strings.Trim(params.Get("search"), " "); v != "" { - listArgs.Search = v - } - if v := strings.Trim(params.Get("orderBy"), " "); v != "" { - listArgs.OrderBy = strings.Split(v, ",") - } - if v := strings.Trim(params.Get("fields"), " "); v != "" { - fields := strings.Split(v, ",") - idNotPresent := true - for i := 0; i < len(fields); i++ { - field := strings.Trim(fields[i], " ") - if field == "" { // skip leading/trailing commas and spaces - continue - } - if field == "id" { - idNotPresent = false - } - listArgs.Fields = append(listArgs.Fields, field) - } - if idNotPresent { - listArgs.Fields = append(listArgs.Fields, "id") - } - } - - return listArgs -} diff --git a/pkg/services/util.go b/pkg/services/util.go deleted file mode 100755 index 597ba8d..0000000 --- a/pkg/services/util.go +++ /dev/null @@ -1,51 +0,0 @@ -package services - -import ( - e "errors" - "strings" - - "gorm.io/gorm" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" -) - -// Field names suspected to contain personally identifiable information -var piiFields = []string{ - "username", - "first_name", - "last_name", - "email", - "address", -} - -func handleGetError(resourceType, field string, value interface{}, err error) *errors.ServiceError { - // Sanitize errors of any personally identifiable information - for _, f := range piiFields { - if field == f { - value = "" - break - } - } - if e.Is(err, gorm.ErrRecordNotFound) { - return errors.NotFound("%s with %s='%v' not found", resourceType, field, value) - } - return errors.GeneralError("Unable to find %s with %s='%v': %s", resourceType, field, value, err) -} - -func handleCreateError(resourceType string, err error) *errors.ServiceError { - if strings.Contains(err.Error(), "violates unique constraint") { - return errors.Conflict("This %s already exists", resourceType) - } - return errors.GeneralError("Unable to create %s: %s", resourceType, err.Error()) -} - -func handleUpdateError(resourceType string, err error) *errors.ServiceError { - if strings.Contains(err.Error(), "violates unique constraint") { - return errors.Conflict("Changes to %s conflict with existing records", resourceType) - } - return errors.GeneralError("Unable to update %s: %s", resourceType, err.Error()) -} - -func handleDeleteError(resourceType string, err error) *errors.ServiceError { - return errors.GeneralError("Unable to delete %s: %s", resourceType, err.Error()) -} diff --git a/pkg/util/utils.go b/pkg/util/utils.go deleted file mode 100755 index 1d261bc..0000000 --- a/pkg/util/utils.go +++ /dev/null @@ -1,56 +0,0 @@ -package util - -import ( - "context" - "fmt" -) - -// ToPtr returns a pointer copy of value. -func ToPtr[T any](v T) *T { - return &v -} - -// FromPtr returns the pointer value or empty. -func FromPtr[T any](v *T) T { - if v == nil { - return Empty[T]() - } - return *v -} - -// FromEmptyPtr emulates ToPtr(FromPtr(x)) sequence -func FromEmptyPtr[T any](v *T) *T { - if v == nil { - x := Empty[T]() - return &x - } - return v -} - -// Empty returns an empty value of type T. -func Empty[T any]() T { - var zero T - return zero -} - -func EmptyStringToNil(a string) *string { - if a == "" { - return nil - } - return &a -} - -func NilToEmptyString(a *string) string { - if a == nil { - return "" - } - return *a -} - -func GetAccountIDFromContext(ctx context.Context) string { - accountID := ctx.Value("accountID") - if accountID == nil { - return "" - } - return fmt.Sprintf("%v", accountID) -} diff --git a/pkg/dao/accessToken.go b/plugins/accessTokens/dao.go similarity index 62% rename from pkg/dao/accessToken.go rename to plugins/accessTokens/dao.go index 35edf77..a126eba 100644 --- a/pkg/dao/accessToken.go +++ b/plugins/accessTokens/dao.go @@ -1,21 +1,21 @@ -package dao +package accessTokens import ( "context" "gorm.io/gorm/clause" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/db" ) type AccessTokenDao interface { - Get(ctx context.Context, id string) (*api.AccessToken, error) - Create(ctx context.Context, accessToken *api.AccessToken) (*api.AccessToken, error) - Replace(ctx context.Context, accessToken *api.AccessToken) (*api.AccessToken, error) + Get(ctx context.Context, id string) (*AccessToken, error) + Create(ctx context.Context, accessToken *AccessToken) (*AccessToken, error) + Replace(ctx context.Context, accessToken *AccessToken) (*AccessToken, error) Delete(ctx context.Context, id string) error - FindByIDs(ctx context.Context, ids []string) (api.AccessTokenList, error) - All(ctx context.Context) (api.AccessTokenList, error) + FindByIDs(ctx context.Context, ids []string) (AccessTokenList, error) + All(ctx context.Context) (AccessTokenList, error) } var _ AccessTokenDao = &sqlAccessTokenDao{} @@ -28,16 +28,16 @@ func NewAccessTokenDao(sessionFactory *db.SessionFactory) AccessTokenDao { return &sqlAccessTokenDao{sessionFactory: sessionFactory} } -func (d *sqlAccessTokenDao) Get(ctx context.Context, id string) (*api.AccessToken, error) { +func (d *sqlAccessTokenDao) Get(ctx context.Context, id string) (*AccessToken, error) { g2 := (*d.sessionFactory).New(ctx) - var accessToken api.AccessToken + var accessToken AccessToken if err := g2.Take(&accessToken, "id = ?", id).Error; err != nil { return nil, err } return &accessToken, nil } -func (d *sqlAccessTokenDao) Create(ctx context.Context, accessToken *api.AccessToken) (*api.AccessToken, error) { +func (d *sqlAccessTokenDao) Create(ctx context.Context, accessToken *AccessToken) (*AccessToken, error) { g2 := (*d.sessionFactory).New(ctx) if err := g2.Omit(clause.Associations).Create(accessToken).Error; err != nil { db.MarkForRollback(ctx, err) @@ -46,7 +46,7 @@ func (d *sqlAccessTokenDao) Create(ctx context.Context, accessToken *api.AccessT return accessToken, nil } -func (d *sqlAccessTokenDao) Replace(ctx context.Context, accessToken *api.AccessToken) (*api.AccessToken, error) { +func (d *sqlAccessTokenDao) Replace(ctx context.Context, accessToken *AccessToken) (*AccessToken, error) { g2 := (*d.sessionFactory).New(ctx) if err := g2.Omit(clause.Associations).Save(accessToken).Error; err != nil { db.MarkForRollback(ctx, err) @@ -57,25 +57,25 @@ func (d *sqlAccessTokenDao) Replace(ctx context.Context, accessToken *api.Access func (d *sqlAccessTokenDao) Delete(ctx context.Context, id string) error { g2 := (*d.sessionFactory).New(ctx) - if err := g2.Omit(clause.Associations).Delete(&api.AccessToken{Meta: api.Meta{ID: id}}).Error; err != nil { + if err := g2.Omit(clause.Associations).Delete(&AccessToken{Meta: api.Meta{ID: id}}).Error; err != nil { db.MarkForRollback(ctx, err) return err } return nil } -func (d *sqlAccessTokenDao) FindByIDs(ctx context.Context, ids []string) (api.AccessTokenList, error) { +func (d *sqlAccessTokenDao) FindByIDs(ctx context.Context, ids []string) (AccessTokenList, error) { g2 := (*d.sessionFactory).New(ctx) - accessTokens := api.AccessTokenList{} + accessTokens := AccessTokenList{} if err := g2.Where("id in (?)", ids).Find(&accessTokens).Error; err != nil { return nil, err } return accessTokens, nil } -func (d *sqlAccessTokenDao) All(ctx context.Context) (api.AccessTokenList, error) { +func (d *sqlAccessTokenDao) All(ctx context.Context) (AccessTokenList, error) { g2 := (*d.sessionFactory).New(ctx) - accessTokens := api.AccessTokenList{} + accessTokens := AccessTokenList{} if err := g2.Find(&accessTokens).Error; err != nil { return nil, err } diff --git a/pkg/handlers/accessToken.go b/plugins/accessTokens/handler.go similarity index 62% rename from pkg/handlers/accessToken.go rename to plugins/accessTokens/handler.go index 3ceadba..718fac9 100644 --- a/pkg/handlers/accessToken.go +++ b/plugins/accessTokens/handler.go @@ -1,25 +1,23 @@ -package handlers +package accessTokens import ( "net/http" "github.com/gorilla/mux" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/presenters" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" + "github.com/openshift-online/rh-trex/pkg/api/presenters" + "github.com/openshift-online/rh-trex/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/handlers" + "github.com/openshift-online/rh-trex/pkg/services" ) -var _ RestHandler = accessTokenHandler{} - type accessTokenHandler struct { - accessToken services.AccessTokenService + accessToken AccessTokenService generic services.GenericService } -func NewAccessTokenHandler(accessToken services.AccessTokenService, generic services.GenericService) *accessTokenHandler { +func NewAccessTokenHandler(accessToken AccessTokenService, generic services.GenericService) *accessTokenHandler { return &accessTokenHandler{ accessToken: accessToken, generic: generic, @@ -28,33 +26,33 @@ func NewAccessTokenHandler(accessToken services.AccessTokenService, generic serv func (h accessTokenHandler) Create(w http.ResponseWriter, r *http.Request) { var accessToken openapi.AccessToken - cfg := &handlerConfig{ - &accessToken, - []validate{ - validateEmpty(&accessToken, "Id", "id"), + cfg := &handlers.HandlerConfig{ + Body: &accessToken, + Validators: []handlers.Validate{ + handlers.ValidateEmpty(&accessToken, "Id", "id"), }, - func() (interface{}, *errors.ServiceError) { + Action: func() (interface{}, *errors.ServiceError) { ctx := r.Context() - accessTokenModel := presenters.ConvertAccessToken(accessToken) + accessTokenModel := ConvertAccessToken(accessToken) accessTokenModel, err := h.accessToken.Create(ctx, accessTokenModel) if err != nil { return nil, err } - return presenters.PresentAccessToken(accessTokenModel), nil + return PresentAccessToken(accessTokenModel), nil }, - handleError, + ErrorHandler: handlers.HandleError, } - handle(w, r, cfg, http.StatusCreated) + handlers.Handle(w, r, cfg, http.StatusCreated) } func (h accessTokenHandler) Patch(w http.ResponseWriter, r *http.Request) { var patch openapi.AccessTokenPatchRequest - cfg := &handlerConfig{ - &patch, - []validate{}, - func() (interface{}, *errors.ServiceError) { + cfg := &handlers.HandlerConfig{ + Body: &patch, + Validators: []handlers.Validate{}, + Action: func() (interface{}, *errors.ServiceError) { ctx := r.Context() id := mux.Vars(r)["id"] found, err := h.accessToken.Get(ctx, id) @@ -62,27 +60,25 @@ func (h accessTokenHandler) Patch(w http.ResponseWriter, r *http.Request) { return nil, err } - //patch a field - accessTokenModel, err := h.accessToken.Replace(ctx, found) if err != nil { return nil, err } - return presenters.PresentAccessToken(accessTokenModel), nil + return PresentAccessToken(accessTokenModel), nil }, - handleError, + ErrorHandler: handlers.HandleError, } - handle(w, r, cfg, http.StatusOK) + handlers.Handle(w, r, cfg, http.StatusOK) } func (h accessTokenHandler) List(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ + cfg := &handlers.HandlerConfig{ Action: func() (interface{}, *errors.ServiceError) { ctx := r.Context() listArgs := services.NewListArguments(r.URL.Query()) - var accessTokens []api.AccessToken + var accessTokens []AccessToken paging, err := h.generic.List(ctx, "username", listArgs, &accessTokens) if err != nil { return nil, err @@ -96,7 +92,7 @@ func (h accessTokenHandler) List(w http.ResponseWriter, r *http.Request) { } for _, accessToken := range accessTokens { - converted := presenters.PresentAccessToken(&accessToken) + converted := PresentAccessToken(&accessToken) accessTokenList.Items = append(accessTokenList.Items, converted) } if listArgs.Fields != nil { @@ -110,11 +106,11 @@ func (h accessTokenHandler) List(w http.ResponseWriter, r *http.Request) { }, } - handleList(w, r, cfg) + handlers.HandleList(w, r, cfg) } func (h accessTokenHandler) Get(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ + cfg := &handlers.HandlerConfig{ Action: func() (interface{}, *errors.ServiceError) { id := mux.Vars(r)["id"] ctx := r.Context() @@ -123,18 +119,18 @@ func (h accessTokenHandler) Get(w http.ResponseWriter, r *http.Request) { return nil, err } - return presenters.PresentAccessToken(accessToken), nil + return PresentAccessToken(accessToken), nil }, } - handleGet(w, r, cfg) + handlers.HandleGet(w, r, cfg) } func (h accessTokenHandler) Delete(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ + cfg := &handlers.HandlerConfig{ Action: func() (interface{}, *errors.ServiceError) { return nil, errors.NotImplemented("delete") }, } - handleDelete(w, r, cfg, http.StatusNoContent) + handlers.HandleDelete(w, r, cfg, http.StatusNoContent) } diff --git a/pkg/db/migrations/202511211404_add_accessTokens.go b/plugins/accessTokens/migration.go similarity index 73% rename from pkg/db/migrations/202511211404_add_accessTokens.go rename to plugins/accessTokens/migration.go index 758948d..ca0dbd8 100644 --- a/pkg/db/migrations/202511211404_add_accessTokens.go +++ b/plugins/accessTokens/migration.go @@ -1,14 +1,14 @@ -package migrations +package accessTokens import ( - "gorm.io/gorm" - "github.com/go-gormigrate/gormigrate/v2" + "github.com/openshift-online/rh-trex/pkg/db" + "gorm.io/gorm" ) -func addAccessTokens() *gormigrate.Migration { +func migration() *gormigrate.Migration { type AccessToken struct { - Model + db.Model } return &gormigrate.Migration{ diff --git a/pkg/dao/mocks/accessToken.go b/plugins/accessTokens/mock_dao.go similarity index 58% rename from pkg/dao/mocks/accessToken.go rename to plugins/accessTokens/mock_dao.go index 95cf703..fc229e9 100644 --- a/pkg/dao/mocks/accessToken.go +++ b/plugins/accessTokens/mock_dao.go @@ -1,26 +1,24 @@ -package mocks +package accessTokens import ( "context" "gorm.io/gorm" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/errors" ) -var _ dao.AccessTokenDao = &accessTokenDaoMock{} +var _ AccessTokenDao = &accessTokenDaoMock{} type accessTokenDaoMock struct { - accessTokens api.AccessTokenList + accessTokens AccessTokenList } -func NewAccessTokenDao() *accessTokenDaoMock { +func NewMockAccessTokenDao() *accessTokenDaoMock { return &accessTokenDaoMock{} } -func (d *accessTokenDaoMock) Get(ctx context.Context, id string) (*api.AccessToken, error) { +func (d *accessTokenDaoMock) Get(ctx context.Context, id string) (*AccessToken, error) { for _, accessToken := range d.accessTokens { if accessToken.ID == id { return accessToken, nil @@ -29,12 +27,12 @@ func (d *accessTokenDaoMock) Get(ctx context.Context, id string) (*api.AccessTok return nil, gorm.ErrRecordNotFound } -func (d *accessTokenDaoMock) Create(ctx context.Context, accessToken *api.AccessToken) (*api.AccessToken, error) { +func (d *accessTokenDaoMock) Create(ctx context.Context, accessToken *AccessToken) (*AccessToken, error) { d.accessTokens = append(d.accessTokens, accessToken) return accessToken, nil } -func (d *accessTokenDaoMock) Replace(ctx context.Context, accessToken *api.AccessToken) (*api.AccessToken, error) { +func (d *accessTokenDaoMock) Replace(ctx context.Context, accessToken *AccessToken) (*AccessToken, error) { return nil, errors.NotImplemented("AccessToken").AsError() } @@ -42,10 +40,10 @@ func (d *accessTokenDaoMock) Delete(ctx context.Context, id string) error { return errors.NotImplemented("AccessToken").AsError() } -func (d *accessTokenDaoMock) FindByIDs(ctx context.Context, ids []string) (api.AccessTokenList, error) { +func (d *accessTokenDaoMock) FindByIDs(ctx context.Context, ids []string) (AccessTokenList, error) { return nil, errors.NotImplemented("AccessToken").AsError() } -func (d *accessTokenDaoMock) All(ctx context.Context) (api.AccessTokenList, error) { +func (d *accessTokenDaoMock) All(ctx context.Context) (AccessTokenList, error) { return d.accessTokens, nil } diff --git a/pkg/api/accessToken.go b/plugins/accessTokens/model.go similarity index 74% rename from pkg/api/accessToken.go rename to plugins/accessTokens/model.go index e260c23..dfb74cb 100644 --- a/pkg/api/accessToken.go +++ b/plugins/accessTokens/model.go @@ -1,9 +1,13 @@ -package api +package accessTokens -import "gorm.io/gorm" +import ( + "gorm.io/gorm" + + "github.com/openshift-online/rh-trex/pkg/api" +) type AccessToken struct { - Meta + api.Meta } type AccessTokenList []*AccessToken @@ -18,7 +22,7 @@ func (l AccessTokenList) Index() AccessTokenIndex { } func (d *AccessToken) BeforeCreate(tx *gorm.DB) error { - d.ID = NewID() + d.ID = api.NewID() return nil } diff --git a/plugins/accessTokens/plugin.go b/plugins/accessTokens/plugin.go index d2ad9a2..97da7ad 100644 --- a/plugins/accessTokens/plugin.go +++ b/plugins/accessTokens/plugin.go @@ -5,35 +5,30 @@ import ( "github.com/gorilla/mux" "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments/registry" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/server" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/presenters" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/auth" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/controllers" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/handlers" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" - "github.com/openshift-hyperfleet/registry-credentials-service/plugins/events" - "github.com/openshift-hyperfleet/registry-credentials-service/plugins/generic" + "github.com/openshift-online/rh-trex/plugins/events" + "github.com/openshift-online/rh-trex/plugins/generic" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/api/presenters" + "github.com/openshift-online/rh-trex/pkg/auth" + "github.com/openshift-online/rh-trex/pkg/controllers" + "github.com/openshift-online/rh-trex/pkg/db" + pkgregistry "github.com/openshift-online/rh-trex/pkg/registry" + pkgserver "github.com/openshift-online/rh-trex/pkg/server" ) -// ServiceLocator Service Locator -type ServiceLocator func() services.AccessTokenService +type ServiceLocator func() AccessTokenService func NewServiceLocator(env *environments.Env) ServiceLocator { - return func() services.AccessTokenService { - return services.NewAccessTokenService( + return func() AccessTokenService { + return NewAccessTokenService( db.NewAdvisoryLockFactory(env.Database.SessionFactory), - dao.NewAccessTokenDao(&env.Database.SessionFactory), + NewAccessTokenDao(&env.Database.SessionFactory), events.Service(&env.Services), ) } } -// Service helper function to get the accessToken service from the registry -func Service(s *environments.Services) services.AccessTokenService { +func Service(s *environments.Services) AccessTokenService { if s == nil { return nil } @@ -45,15 +40,15 @@ func Service(s *environments.Services) services.AccessTokenService { } func init() { - // Service registration - registry.RegisterService("AccessTokens", func(env interface{}) interface{} { + db.RegisterMigration(migration()) + + pkgregistry.RegisterService("AccessTokens", func(env interface{}) interface{} { return NewServiceLocator(env.(*environments.Env)) }) - // Routes registration - server.RegisterRoutes("accessTokens", func(apiV1Router *mux.Router, services server.ServicesInterface, authMiddleware auth.JWTMiddleware, authzMiddleware auth.AuthorizationMiddleware) { + pkgserver.RegisterRoutes("accessTokens", func(apiV1Router *mux.Router, services pkgserver.ServicesInterface, authMiddleware auth.JWTMiddleware, authzMiddleware auth.AuthorizationMiddleware) { envServices := services.(*environments.Services) - accessTokenHandler := handlers.NewAccessTokenHandler(Service(envServices), generic.Service(envServices)) + accessTokenHandler := NewAccessTokenHandler(Service(envServices), generic.Service(envServices)) accessTokensRouter := apiV1Router.PathPrefix("/access_tokens").Subrouter() accessTokensRouter.HandleFunc("", accessTokenHandler.List).Methods(http.MethodGet) @@ -65,9 +60,9 @@ func init() { accessTokensRouter.Use(authzMiddleware.AuthorizeApi) }) - // Controller registration - server.RegisterController("AccessTokens", func(manager *controllers.KindControllerManager, services *environments.Services) { - accessTokenServices := Service(services) + pkgserver.RegisterController("AccessTokens", func(manager *controllers.KindControllerManager, services pkgserver.ServicesInterface) { + envServices := services.(*environments.Services) + accessTokenServices := Service(envServices) manager.Add(&controllers.ControllerConfig{ Source: "AccessTokens", @@ -79,9 +74,8 @@ func init() { }) }) - // Presenter registration - presenters.RegisterPath(api.AccessToken{}, "access_tokens") - presenters.RegisterPath(&api.AccessToken{}, "access_tokens") - presenters.RegisterKind(api.AccessToken{}, "AccessToken") - presenters.RegisterKind(&api.AccessToken{}, "AccessToken") + presenters.RegisterPath(AccessToken{}, "access_tokens") + presenters.RegisterPath(&AccessToken{}, "access_tokens") + presenters.RegisterKind(AccessToken{}, "AccessToken") + presenters.RegisterKind(&AccessToken{}, "AccessToken") } diff --git a/plugins/accessTokens/presenter.go b/plugins/accessTokens/presenter.go new file mode 100644 index 0000000..13e5167 --- /dev/null +++ b/plugins/accessTokens/presenter.go @@ -0,0 +1,30 @@ +package accessTokens + +import ( + "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" + "github.com/openshift-online/rh-trex/pkg/api/presenters" + "github.com/openshift-online/rh-trex/pkg/util" +) + +func ConvertAccessToken(accessToken openapi.AccessToken) *AccessToken { + c := &AccessToken{} + c.ID = util.NilToEmptyString(accessToken.Id) + + if accessToken.CreatedAt != nil { + c.CreatedAt = *accessToken.CreatedAt + c.UpdatedAt = *accessToken.UpdatedAt + } + + return c +} + +func PresentAccessToken(accessToken *AccessToken) openapi.AccessToken { + reference := presenters.PresentReference(accessToken.ID, accessToken) + return openapi.AccessToken{ + Id: reference.Id, + Kind: reference.Kind, + Href: reference.Href, + CreatedAt: openapi.PtrTime(accessToken.CreatedAt), + UpdatedAt: openapi.PtrTime(accessToken.UpdatedAt), + } +} diff --git a/pkg/services/accessToken.go b/plugins/accessTokens/service.go similarity index 58% rename from pkg/services/accessToken.go rename to plugins/accessTokens/service.go index 6e55df7..406a936 100644 --- a/pkg/services/accessToken.go +++ b/plugins/accessTokens/service.go @@ -1,30 +1,27 @@ -package services +package accessTokens import ( "context" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/db" + "github.com/openshift-online/rh-trex/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/logger" + "github.com/openshift-online/rh-trex/pkg/services" ) type AccessTokenService interface { - Get(ctx context.Context, id string) (*api.AccessToken, *errors.ServiceError) - Create(ctx context.Context, accessToken *api.AccessToken) (*api.AccessToken, *errors.ServiceError) - Replace(ctx context.Context, accessToken *api.AccessToken) (*api.AccessToken, *errors.ServiceError) + Get(ctx context.Context, id string) (*AccessToken, *errors.ServiceError) + Create(ctx context.Context, accessToken *AccessToken) (*AccessToken, *errors.ServiceError) + Replace(ctx context.Context, accessToken *AccessToken) (*AccessToken, *errors.ServiceError) Delete(ctx context.Context, id string) *errors.ServiceError - All(ctx context.Context) (api.AccessTokenList, *errors.ServiceError) - - FindByIDs(ctx context.Context, ids []string) (api.AccessTokenList, *errors.ServiceError) - - // idempotent functions for the control plane, but can also be called synchronously by any actor + All(ctx context.Context) (AccessTokenList, *errors.ServiceError) + FindByIDs(ctx context.Context, ids []string) (AccessTokenList, *errors.ServiceError) OnUpsert(ctx context.Context, id string) error OnDelete(ctx context.Context, id string) error } -func NewAccessTokenService(lockFactory db.LockFactory, accessTokenDao dao.AccessTokenDao, events EventService) AccessTokenService { +func NewAccessTokenService(lockFactory db.LockFactory, accessTokenDao AccessTokenDao, events services.EventService) AccessTokenService { return &sqlAccessTokenService{ lockFactory: lockFactory, accessTokenDao: accessTokenDao, @@ -36,22 +33,22 @@ var _ AccessTokenService = &sqlAccessTokenService{} type sqlAccessTokenService struct { lockFactory db.LockFactory - accessTokenDao dao.AccessTokenDao - events EventService + accessTokenDao AccessTokenDao + events services.EventService } -func (s *sqlAccessTokenService) Get(ctx context.Context, id string) (*api.AccessToken, *errors.ServiceError) { +func (s *sqlAccessTokenService) Get(ctx context.Context, id string) (*AccessToken, *errors.ServiceError) { accessToken, err := s.accessTokenDao.Get(ctx, id) if err != nil { - return nil, handleGetError("AccessToken", "id", id, err) + return nil, services.HandleGetError("AccessToken", "id", id, err) } return accessToken, nil } -func (s *sqlAccessTokenService) Create(ctx context.Context, accessToken *api.AccessToken) (*api.AccessToken, *errors.ServiceError) { +func (s *sqlAccessTokenService) Create(ctx context.Context, accessToken *AccessToken) (*AccessToken, *errors.ServiceError) { accessToken, err := s.accessTokenDao.Create(ctx, accessToken) if err != nil { - return nil, handleCreateError("AccessToken", err) + return nil, services.HandleCreateError("AccessToken", err) } _, evErr := s.events.Create(ctx, &api.Event{ @@ -60,16 +57,16 @@ func (s *sqlAccessTokenService) Create(ctx context.Context, accessToken *api.Acc EventType: api.CreateEventType, }) if evErr != nil { - return nil, handleCreateError("AccessToken", evErr) + return nil, services.HandleCreateError("AccessToken", evErr) } return accessToken, nil } -func (s *sqlAccessTokenService) Replace(ctx context.Context, accessToken *api.AccessToken) (*api.AccessToken, *errors.ServiceError) { +func (s *sqlAccessTokenService) Replace(ctx context.Context, accessToken *AccessToken) (*AccessToken, *errors.ServiceError) { accessToken, err := s.accessTokenDao.Replace(ctx, accessToken) if err != nil { - return nil, handleUpdateError("AccessToken", err) + return nil, services.HandleUpdateError("AccessToken", err) } _, evErr := s.events.Create(ctx, &api.Event{ @@ -78,7 +75,7 @@ func (s *sqlAccessTokenService) Replace(ctx context.Context, accessToken *api.Ac EventType: api.UpdateEventType, }) if evErr != nil { - return nil, handleUpdateError("AccessToken", evErr) + return nil, services.HandleUpdateError("AccessToken", evErr) } return accessToken, nil @@ -86,7 +83,7 @@ func (s *sqlAccessTokenService) Replace(ctx context.Context, accessToken *api.Ac func (s *sqlAccessTokenService) Delete(ctx context.Context, id string) *errors.ServiceError { if err := s.accessTokenDao.Delete(ctx, id); err != nil { - return handleDeleteError("AccessToken", errors.GeneralError("Unable to delete accessToken: %s", err)) + return services.HandleDeleteError("AccessToken", errors.GeneralError("Unable to delete accessToken: %s", err)) } _, evErr := s.events.Create(ctx, &api.Event{ @@ -95,13 +92,13 @@ func (s *sqlAccessTokenService) Delete(ctx context.Context, id string) *errors.S EventType: api.DeleteEventType, }) if evErr != nil { - return handleDeleteError("AccessToken", evErr) + return services.HandleDeleteError("AccessToken", evErr) } return nil } -func (s *sqlAccessTokenService) FindByIDs(ctx context.Context, ids []string) (api.AccessTokenList, *errors.ServiceError) { +func (s *sqlAccessTokenService) FindByIDs(ctx context.Context, ids []string) (AccessTokenList, *errors.ServiceError) { accessTokens, err := s.accessTokenDao.FindByIDs(ctx, ids) if err != nil { return nil, errors.GeneralError("Unable to get all accessTokens: %s", err) @@ -109,7 +106,7 @@ func (s *sqlAccessTokenService) FindByIDs(ctx context.Context, ids []string) (ap return accessTokens, nil } -func (s *sqlAccessTokenService) All(ctx context.Context) (api.AccessTokenList, *errors.ServiceError) { +func (s *sqlAccessTokenService) All(ctx context.Context) (AccessTokenList, *errors.ServiceError) { accessTokens, err := s.accessTokenDao.All(ctx) if err != nil { return nil, errors.GeneralError("Unable to get all accessTokens: %s", err) diff --git a/plugins/dinosaurs/plugin.go b/plugins/dinosaurs/plugin.go deleted file mode 100755 index 3650152..0000000 --- a/plugins/dinosaurs/plugin.go +++ /dev/null @@ -1,87 +0,0 @@ -package dinosaurs - -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments/registry" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/server" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/presenters" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/auth" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/controllers" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/handlers" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" - "github.com/openshift-hyperfleet/registry-credentials-service/plugins/events" - "github.com/openshift-hyperfleet/registry-credentials-service/plugins/generic" -) - -// ServiceLocator Service Locator -type ServiceLocator func() services.DinosaurService - -func NewServiceLocator(env *environments.Env) ServiceLocator { - return func() services.DinosaurService { - return services.NewDinosaurService( - db.NewAdvisoryLockFactory(env.Database.SessionFactory), - dao.NewDinosaurDao(&env.Database.SessionFactory), - events.Service(&env.Services), - ) - } -} - -// Service helper function to get the dinosaur service from the registry -func Service(s *environments.Services) services.DinosaurService { - if s == nil { - return nil - } - if obj := s.GetService("Dinosaurs"); obj != nil { - locator := obj.(ServiceLocator) - return locator() - } - return nil -} - -func init() { - // Service registration - registry.RegisterService("Dinosaurs", func(env interface{}) interface{} { - return NewServiceLocator(env.(*environments.Env)) - }) - - // Routes registration - server.RegisterRoutes("dinosaurs", func(apiV1Router *mux.Router, services server.ServicesInterface, authMiddleware auth.JWTMiddleware, authzMiddleware auth.AuthorizationMiddleware) { - envServices := services.(*environments.Services) - dinosaurHandler := handlers.NewDinosaurHandler(Service(envServices), generic.Service(envServices)) - - dinosaursRouter := apiV1Router.PathPrefix("/dinosaurs").Subrouter() - dinosaursRouter.HandleFunc("", dinosaurHandler.List).Methods(http.MethodGet) - dinosaursRouter.HandleFunc("/{id}", dinosaurHandler.Get).Methods(http.MethodGet) - dinosaursRouter.HandleFunc("", dinosaurHandler.Create).Methods(http.MethodPost) - dinosaursRouter.HandleFunc("/{id}", dinosaurHandler.Patch).Methods(http.MethodPatch) - dinosaursRouter.HandleFunc("/{id}", dinosaurHandler.Delete).Methods(http.MethodDelete) - dinosaursRouter.Use(authMiddleware.AuthenticateAccountJWT) - dinosaursRouter.Use(authzMiddleware.AuthorizeApi) - }) - - // Controller registration - server.RegisterController("Dinosaurs", func(manager *controllers.KindControllerManager, services *environments.Services) { - dinoServices := Service(services) - - manager.Add(&controllers.ControllerConfig{ - Source: "Dinosaurs", - Handlers: map[api.EventType][]controllers.ControllerHandlerFunc{ - api.CreateEventType: {dinoServices.OnUpsert}, - api.UpdateEventType: {dinoServices.OnUpsert}, - api.DeleteEventType: {dinoServices.OnDelete}, - }, - }) - }) - - // Presenter registration - presenters.RegisterPath(api.Dinosaur{}, "dinosaurs") - presenters.RegisterPath(&api.Dinosaur{}, "dinosaurs") - presenters.RegisterKind(api.Dinosaur{}, "Dinosaur") - presenters.RegisterKind(&api.Dinosaur{}, "Dinosaur") -} diff --git a/plugins/events/plugin.go b/plugins/events/plugin.go deleted file mode 100755 index acf27eb..0000000 --- a/plugins/events/plugin.go +++ /dev/null @@ -1,36 +0,0 @@ -package events - -import ( - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments/registry" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" -) - -// ServiceLocator Service Locator -type ServiceLocator func() services.EventService - -func NewServiceLocator(env *environments.Env) ServiceLocator { - return func() services.EventService { - return services.NewEventService(dao.NewEventDao(&env.Database.SessionFactory)) - } -} - -// Service helper function to get the event service from the registry -func Service(s *environments.Services) services.EventService { - if s == nil { - return nil - } - if obj := s.GetService("Events"); obj != nil { - locator := obj.(ServiceLocator) - return locator() - } - return nil -} - -func init() { - // Service registration - registry.RegisterService("Events", func(env interface{}) interface{} { - return NewServiceLocator(env.(*environments.Env)) - }) -} diff --git a/plugins/generic/plugin.go b/plugins/generic/plugin.go deleted file mode 100755 index 30bc0ea..0000000 --- a/plugins/generic/plugin.go +++ /dev/null @@ -1,36 +0,0 @@ -package generic - -import ( - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments/registry" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" -) - -// ServiceLocator Service Locator -type ServiceLocator func() services.GenericService - -func NewServiceLocator(env *environments.Env) ServiceLocator { - return func() services.GenericService { - return services.NewGenericService(dao.NewGenericDao(&env.Database.SessionFactory)) - } -} - -// Service helper function to get the generic service from the registry -func Service(s *environments.Services) services.GenericService { - if s == nil { - return nil - } - if obj := s.GetService("Generic"); obj != nil { - locator := obj.(ServiceLocator) - return locator() - } - return nil -} - -func init() { - // Service registration - registry.RegisterService("Generic", func(env interface{}) interface{} { - return NewServiceLocator(env.(*environments.Env)) - }) -} diff --git a/pkg/dao/registryCredential.go b/plugins/registryCredentials/dao.go similarity index 59% rename from pkg/dao/registryCredential.go rename to plugins/registryCredentials/dao.go index bf29b64..58aa596 100644 --- a/pkg/dao/registryCredential.go +++ b/plugins/registryCredentials/dao.go @@ -1,21 +1,21 @@ -package dao +package registryCredentials import ( "context" "gorm.io/gorm/clause" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/db" ) type RegistryCredentialDao interface { - Get(ctx context.Context, id string) (*api.RegistryCredential, error) - Create(ctx context.Context, registryCredential *api.RegistryCredential) (*api.RegistryCredential, error) - Replace(ctx context.Context, registryCredential *api.RegistryCredential) (*api.RegistryCredential, error) + Get(ctx context.Context, id string) (*RegistryCredential, error) + Create(ctx context.Context, registryCredential *RegistryCredential) (*RegistryCredential, error) + Replace(ctx context.Context, registryCredential *RegistryCredential) (*RegistryCredential, error) Delete(ctx context.Context, id string) error - FindByIDs(ctx context.Context, ids []string) (api.RegistryCredentialList, error) - All(ctx context.Context) (api.RegistryCredentialList, error) + FindByIDs(ctx context.Context, ids []string) (RegistryCredentialList, error) + All(ctx context.Context) (RegistryCredentialList, error) } var _ RegistryCredentialDao = &sqlRegistryCredentialDao{} @@ -28,16 +28,16 @@ func NewRegistryCredentialDao(sessionFactory *db.SessionFactory) RegistryCredent return &sqlRegistryCredentialDao{sessionFactory: sessionFactory} } -func (d *sqlRegistryCredentialDao) Get(ctx context.Context, id string) (*api.RegistryCredential, error) { +func (d *sqlRegistryCredentialDao) Get(ctx context.Context, id string) (*RegistryCredential, error) { g2 := (*d.sessionFactory).New(ctx) - var registryCredential api.RegistryCredential + var registryCredential RegistryCredential if err := g2.Take(®istryCredential, "id = ?", id).Error; err != nil { return nil, err } return ®istryCredential, nil } -func (d *sqlRegistryCredentialDao) Create(ctx context.Context, registryCredential *api.RegistryCredential) (*api.RegistryCredential, error) { +func (d *sqlRegistryCredentialDao) Create(ctx context.Context, registryCredential *RegistryCredential) (*RegistryCredential, error) { g2 := (*d.sessionFactory).New(ctx) if err := g2.Omit(clause.Associations).Create(registryCredential).Error; err != nil { db.MarkForRollback(ctx, err) @@ -46,7 +46,7 @@ func (d *sqlRegistryCredentialDao) Create(ctx context.Context, registryCredentia return registryCredential, nil } -func (d *sqlRegistryCredentialDao) Replace(ctx context.Context, registryCredential *api.RegistryCredential) (*api.RegistryCredential, error) { +func (d *sqlRegistryCredentialDao) Replace(ctx context.Context, registryCredential *RegistryCredential) (*RegistryCredential, error) { g2 := (*d.sessionFactory).New(ctx) if err := g2.Omit(clause.Associations).Save(registryCredential).Error; err != nil { db.MarkForRollback(ctx, err) @@ -57,25 +57,25 @@ func (d *sqlRegistryCredentialDao) Replace(ctx context.Context, registryCredenti func (d *sqlRegistryCredentialDao) Delete(ctx context.Context, id string) error { g2 := (*d.sessionFactory).New(ctx) - if err := g2.Omit(clause.Associations).Delete(&api.RegistryCredential{Meta: api.Meta{ID: id}}).Error; err != nil { + if err := g2.Omit(clause.Associations).Delete(&RegistryCredential{Meta: api.Meta{ID: id}}).Error; err != nil { db.MarkForRollback(ctx, err) return err } return nil } -func (d *sqlRegistryCredentialDao) FindByIDs(ctx context.Context, ids []string) (api.RegistryCredentialList, error) { +func (d *sqlRegistryCredentialDao) FindByIDs(ctx context.Context, ids []string) (RegistryCredentialList, error) { g2 := (*d.sessionFactory).New(ctx) - registryCredentials := api.RegistryCredentialList{} + registryCredentials := RegistryCredentialList{} if err := g2.Where("id in (?)", ids).Find(®istryCredentials).Error; err != nil { return nil, err } return registryCredentials, nil } -func (d *sqlRegistryCredentialDao) All(ctx context.Context) (api.RegistryCredentialList, error) { +func (d *sqlRegistryCredentialDao) All(ctx context.Context) (RegistryCredentialList, error) { g2 := (*d.sessionFactory).New(ctx) - registryCredentials := api.RegistryCredentialList{} + registryCredentials := RegistryCredentialList{} if err := g2.Find(®istryCredentials).Error; err != nil { return nil, err } diff --git a/pkg/handlers/registryCredential.go b/plugins/registryCredentials/handler.go similarity index 62% rename from pkg/handlers/registryCredential.go rename to plugins/registryCredentials/handler.go index 1eded6d..e7e50d4 100644 --- a/pkg/handlers/registryCredential.go +++ b/plugins/registryCredentials/handler.go @@ -1,25 +1,23 @@ -package handlers +package registryCredentials import ( "net/http" "github.com/gorilla/mux" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/presenters" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" + "github.com/openshift-online/rh-trex/pkg/api/presenters" + "github.com/openshift-online/rh-trex/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/handlers" + "github.com/openshift-online/rh-trex/pkg/services" ) -var _ RestHandler = registryCredentialHandler{} - type registryCredentialHandler struct { - registryCredential services.RegistryCredentialService + registryCredential RegistryCredentialService generic services.GenericService } -func NewRegistryCredentialHandler(registryCredential services.RegistryCredentialService, generic services.GenericService) *registryCredentialHandler { +func NewRegistryCredentialHandler(registryCredential RegistryCredentialService, generic services.GenericService) *registryCredentialHandler { return ®istryCredentialHandler{ registryCredential: registryCredential, generic: generic, @@ -28,33 +26,33 @@ func NewRegistryCredentialHandler(registryCredential services.RegistryCredential func (h registryCredentialHandler) Create(w http.ResponseWriter, r *http.Request) { var registryCredential openapi.RegistryCredential - cfg := &handlerConfig{ - ®istryCredential, - []validate{ - validateEmpty(®istryCredential, "Id", "id"), + cfg := &handlers.HandlerConfig{ + Body: ®istryCredential, + Validators: []handlers.Validate{ + handlers.ValidateEmpty(®istryCredential, "Id", "id"), }, - func() (interface{}, *errors.ServiceError) { + Action: func() (interface{}, *errors.ServiceError) { ctx := r.Context() - registryCredentialModel := presenters.ConvertRegistryCredential(registryCredential) + registryCredentialModel := ConvertRegistryCredential(registryCredential) registryCredentialModel, err := h.registryCredential.Create(ctx, registryCredentialModel) if err != nil { return nil, err } - return presenters.PresentRegistryCredential(registryCredentialModel), nil + return PresentRegistryCredential(registryCredentialModel), nil }, - handleError, + ErrorHandler: handlers.HandleError, } - handle(w, r, cfg, http.StatusCreated) + handlers.Handle(w, r, cfg, http.StatusCreated) } func (h registryCredentialHandler) Patch(w http.ResponseWriter, r *http.Request) { var patch openapi.RegistryCredentialPatchRequest - cfg := &handlerConfig{ - &patch, - []validate{}, - func() (interface{}, *errors.ServiceError) { + cfg := &handlers.HandlerConfig{ + Body: &patch, + Validators: []handlers.Validate{}, + Action: func() (interface{}, *errors.ServiceError) { ctx := r.Context() id := mux.Vars(r)["id"] found, err := h.registryCredential.Get(ctx, id) @@ -62,27 +60,25 @@ func (h registryCredentialHandler) Patch(w http.ResponseWriter, r *http.Request) return nil, err } - //patch a field - registryCredentialModel, err := h.registryCredential.Replace(ctx, found) if err != nil { return nil, err } - return presenters.PresentRegistryCredential(registryCredentialModel), nil + return PresentRegistryCredential(registryCredentialModel), nil }, - handleError, + ErrorHandler: handlers.HandleError, } - handle(w, r, cfg, http.StatusOK) + handlers.Handle(w, r, cfg, http.StatusOK) } func (h registryCredentialHandler) List(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ + cfg := &handlers.HandlerConfig{ Action: func() (interface{}, *errors.ServiceError) { ctx := r.Context() listArgs := services.NewListArguments(r.URL.Query()) - var registryCredentials []api.RegistryCredential + var registryCredentials []RegistryCredential paging, err := h.generic.List(ctx, "username", listArgs, ®istryCredentials) if err != nil { return nil, err @@ -96,7 +92,7 @@ func (h registryCredentialHandler) List(w http.ResponseWriter, r *http.Request) } for _, registryCredential := range registryCredentials { - converted := presenters.PresentRegistryCredential(®istryCredential) + converted := PresentRegistryCredential(®istryCredential) registryCredentialList.Items = append(registryCredentialList.Items, converted) } if listArgs.Fields != nil { @@ -110,11 +106,11 @@ func (h registryCredentialHandler) List(w http.ResponseWriter, r *http.Request) }, } - handleList(w, r, cfg) + handlers.HandleList(w, r, cfg) } func (h registryCredentialHandler) Get(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ + cfg := &handlers.HandlerConfig{ Action: func() (interface{}, *errors.ServiceError) { id := mux.Vars(r)["id"] ctx := r.Context() @@ -123,18 +119,18 @@ func (h registryCredentialHandler) Get(w http.ResponseWriter, r *http.Request) { return nil, err } - return presenters.PresentRegistryCredential(registryCredential), nil + return PresentRegistryCredential(registryCredential), nil }, } - handleGet(w, r, cfg) + handlers.HandleGet(w, r, cfg) } func (h registryCredentialHandler) Delete(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ + cfg := &handlers.HandlerConfig{ Action: func() (interface{}, *errors.ServiceError) { return nil, errors.NotImplemented("delete") }, } - handleDelete(w, r, cfg, http.StatusNoContent) + handlers.HandleDelete(w, r, cfg, http.StatusNoContent) } diff --git a/pkg/db/migrations/202511211401_add_registryCredentials.go b/plugins/registryCredentials/migration.go similarity index 82% rename from pkg/db/migrations/202511211401_add_registryCredentials.go rename to plugins/registryCredentials/migration.go index 0da4cba..1b876e5 100644 --- a/pkg/db/migrations/202511211401_add_registryCredentials.go +++ b/plugins/registryCredentials/migration.go @@ -1,14 +1,14 @@ -package migrations +package registryCredentials import ( - "gorm.io/gorm" - "github.com/go-gormigrate/gormigrate/v2" + "github.com/openshift-online/rh-trex/pkg/db" + "gorm.io/gorm" ) -func addRegistryCredentials() *gormigrate.Migration { +func migration() *gormigrate.Migration { type RegistryCredential struct { - Model + db.Model Username string `gorm:"not null; index"` Token string `gorm:"not null"` AccountID *string `gorm:"index"` diff --git a/pkg/dao/mocks/registryCredential.go b/plugins/registryCredentials/mock_dao.go similarity index 56% rename from pkg/dao/mocks/registryCredential.go rename to plugins/registryCredentials/mock_dao.go index e798345..000fc27 100644 --- a/pkg/dao/mocks/registryCredential.go +++ b/plugins/registryCredentials/mock_dao.go @@ -1,26 +1,24 @@ -package mocks +package registryCredentials import ( "context" "gorm.io/gorm" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/errors" ) -var _ dao.RegistryCredentialDao = ®istryCredentialDaoMock{} +var _ RegistryCredentialDao = ®istryCredentialDaoMock{} type registryCredentialDaoMock struct { - registryCredentials api.RegistryCredentialList + registryCredentials RegistryCredentialList } -func NewRegistryCredentialDao() *registryCredentialDaoMock { +func NewMockRegistryCredentialDao() *registryCredentialDaoMock { return ®istryCredentialDaoMock{} } -func (d *registryCredentialDaoMock) Get(ctx context.Context, id string) (*api.RegistryCredential, error) { +func (d *registryCredentialDaoMock) Get(ctx context.Context, id string) (*RegistryCredential, error) { for _, registryCredential := range d.registryCredentials { if registryCredential.ID == id { return registryCredential, nil @@ -29,12 +27,12 @@ func (d *registryCredentialDaoMock) Get(ctx context.Context, id string) (*api.Re return nil, gorm.ErrRecordNotFound } -func (d *registryCredentialDaoMock) Create(ctx context.Context, registryCredential *api.RegistryCredential) (*api.RegistryCredential, error) { +func (d *registryCredentialDaoMock) Create(ctx context.Context, registryCredential *RegistryCredential) (*RegistryCredential, error) { d.registryCredentials = append(d.registryCredentials, registryCredential) return registryCredential, nil } -func (d *registryCredentialDaoMock) Replace(ctx context.Context, registryCredential *api.RegistryCredential) (*api.RegistryCredential, error) { +func (d *registryCredentialDaoMock) Replace(ctx context.Context, registryCredential *RegistryCredential) (*RegistryCredential, error) { return nil, errors.NotImplemented("RegistryCredential").AsError() } @@ -42,10 +40,10 @@ func (d *registryCredentialDaoMock) Delete(ctx context.Context, id string) error return errors.NotImplemented("RegistryCredential").AsError() } -func (d *registryCredentialDaoMock) FindByIDs(ctx context.Context, ids []string) (api.RegistryCredentialList, error) { +func (d *registryCredentialDaoMock) FindByIDs(ctx context.Context, ids []string) (RegistryCredentialList, error) { return nil, errors.NotImplemented("RegistryCredential").AsError() } -func (d *registryCredentialDaoMock) All(ctx context.Context) (api.RegistryCredentialList, error) { +func (d *registryCredentialDaoMock) All(ctx context.Context) (RegistryCredentialList, error) { return d.registryCredentials, nil } diff --git a/pkg/api/registryCredential.go b/plugins/registryCredentials/model.go similarity index 90% rename from pkg/api/registryCredential.go rename to plugins/registryCredentials/model.go index 9263271..70820ca 100644 --- a/pkg/api/registryCredential.go +++ b/plugins/registryCredentials/model.go @@ -1,4 +1,4 @@ -package api +package registryCredentials import ( "encoding/base64" @@ -6,11 +6,12 @@ import ( "gorm.io/gorm" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/errors" ) type RegistryCredential struct { - Meta + api.Meta Username string `json:"username"` Token string `json:"token"` AccountID *string `json:"account_id"` @@ -30,7 +31,7 @@ func (l RegistryCredentialList) Index() RegistryCredentialIndex { } func (d *RegistryCredential) BeforeCreate(tx *gorm.DB) error { - d.ID = NewID() + d.ID = api.NewID() return nil } diff --git a/plugins/registryCredentials/plugin.go b/plugins/registryCredentials/plugin.go index c43396e..f5d10cb 100644 --- a/plugins/registryCredentials/plugin.go +++ b/plugins/registryCredentials/plugin.go @@ -5,35 +5,30 @@ import ( "github.com/gorilla/mux" "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments/registry" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/server" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/presenters" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/auth" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/controllers" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/handlers" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" - "github.com/openshift-hyperfleet/registry-credentials-service/plugins/events" - "github.com/openshift-hyperfleet/registry-credentials-service/plugins/generic" + "github.com/openshift-online/rh-trex/plugins/events" + "github.com/openshift-online/rh-trex/plugins/generic" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/api/presenters" + "github.com/openshift-online/rh-trex/pkg/auth" + "github.com/openshift-online/rh-trex/pkg/controllers" + "github.com/openshift-online/rh-trex/pkg/db" + pkgregistry "github.com/openshift-online/rh-trex/pkg/registry" + pkgserver "github.com/openshift-online/rh-trex/pkg/server" ) -// ServiceLocator Service Locator -type ServiceLocator func() services.RegistryCredentialService +type ServiceLocator func() RegistryCredentialService func NewServiceLocator(env *environments.Env) ServiceLocator { - return func() services.RegistryCredentialService { - return services.NewRegistryCredentialService( + return func() RegistryCredentialService { + return NewRegistryCredentialService( db.NewAdvisoryLockFactory(env.Database.SessionFactory), - dao.NewRegistryCredentialDao(&env.Database.SessionFactory), + NewRegistryCredentialDao(&env.Database.SessionFactory), events.Service(&env.Services), ) } } -// Service helper function to get the registryCredential service from the registry -func Service(s *environments.Services) services.RegistryCredentialService { +func Service(s *environments.Services) RegistryCredentialService { if s == nil { return nil } @@ -45,15 +40,15 @@ func Service(s *environments.Services) services.RegistryCredentialService { } func init() { - // Service registration - registry.RegisterService("RegistryCredentials", func(env interface{}) interface{} { + db.RegisterMigration(migration()) + + pkgregistry.RegisterService("RegistryCredentials", func(env interface{}) interface{} { return NewServiceLocator(env.(*environments.Env)) }) - // Routes registration - server.RegisterRoutes("registryCredentials", func(apiV1Router *mux.Router, services server.ServicesInterface, authMiddleware auth.JWTMiddleware, authzMiddleware auth.AuthorizationMiddleware) { + pkgserver.RegisterRoutes("registryCredentials", func(apiV1Router *mux.Router, services pkgserver.ServicesInterface, authMiddleware auth.JWTMiddleware, authzMiddleware auth.AuthorizationMiddleware) { envServices := services.(*environments.Services) - registryCredentialHandler := handlers.NewRegistryCredentialHandler(Service(envServices), generic.Service(envServices)) + registryCredentialHandler := NewRegistryCredentialHandler(Service(envServices), generic.Service(envServices)) registryCredentialsRouter := apiV1Router.PathPrefix("/registry_credentials").Subrouter() registryCredentialsRouter.HandleFunc("", registryCredentialHandler.List).Methods(http.MethodGet) @@ -65,9 +60,9 @@ func init() { registryCredentialsRouter.Use(authzMiddleware.AuthorizeApi) }) - // Controller registration - server.RegisterController("RegistryCredentials", func(manager *controllers.KindControllerManager, services *environments.Services) { - registryCredentialServices := Service(services) + pkgserver.RegisterController("RegistryCredentials", func(manager *controllers.KindControllerManager, services pkgserver.ServicesInterface) { + envServices := services.(*environments.Services) + registryCredentialServices := Service(envServices) manager.Add(&controllers.ControllerConfig{ Source: "RegistryCredentials", @@ -79,9 +74,8 @@ func init() { }) }) - // Presenter registration - presenters.RegisterPath(api.RegistryCredential{}, "registry_credentials") - presenters.RegisterPath(&api.RegistryCredential{}, "registry_credentials") - presenters.RegisterKind(api.RegistryCredential{}, "RegistryCredential") - presenters.RegisterKind(&api.RegistryCredential{}, "RegistryCredential") + presenters.RegisterPath(RegistryCredential{}, "registry_credentials") + presenters.RegisterPath(&RegistryCredential{}, "registry_credentials") + presenters.RegisterKind(RegistryCredential{}, "RegistryCredential") + presenters.RegisterKind(&RegistryCredential{}, "RegistryCredential") } diff --git a/pkg/api/presenters/registryCredential.go b/plugins/registryCredentials/presenter.go similarity index 73% rename from pkg/api/presenters/registryCredential.go rename to plugins/registryCredentials/presenter.go index 290ac64..74df40f 100644 --- a/pkg/api/presenters/registryCredential.go +++ b/plugins/registryCredentials/presenter.go @@ -1,21 +1,19 @@ -package presenters +package registryCredentials import ( - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/util" + "github.com/openshift-online/rh-trex/pkg/api/presenters" + "github.com/openshift-online/rh-trex/pkg/util" ) -func ConvertRegistryCredential(registryCredential openapi.RegistryCredential) *api.RegistryCredential { - c := &api.RegistryCredential{ - Meta: api.Meta{ - ID: util.NilToEmptyString(registryCredential.Id), - }, +func ConvertRegistryCredential(registryCredential openapi.RegistryCredential) *RegistryCredential { + c := &RegistryCredential{ Username: util.NilToEmptyString(registryCredential.Username), Token: util.NilToEmptyString(registryCredential.Token), RegistryID: util.NilToEmptyString(registryCredential.RegistryId), ExternalResourceID: util.NilToEmptyString(registryCredential.ExternalResourceId), } + c.ID = util.NilToEmptyString(registryCredential.Id) if registryCredential.AccountId != nil { c.AccountID = registryCredential.AccountId @@ -29,8 +27,8 @@ func ConvertRegistryCredential(registryCredential openapi.RegistryCredential) *a return c } -func PresentRegistryCredential(registryCredential *api.RegistryCredential) openapi.RegistryCredential { - reference := PresentReference(registryCredential.ID, registryCredential) +func PresentRegistryCredential(registryCredential *RegistryCredential) openapi.RegistryCredential { + reference := presenters.PresentReference(registryCredential.ID, registryCredential) return openapi.RegistryCredential{ Id: reference.Id, Kind: reference.Kind, diff --git a/pkg/services/registryCredential.go b/plugins/registryCredentials/service.go similarity index 59% rename from pkg/services/registryCredential.go rename to plugins/registryCredentials/service.go index e519608..f568b9f 100644 --- a/pkg/services/registryCredential.go +++ b/plugins/registryCredentials/service.go @@ -1,30 +1,27 @@ -package services +package registryCredentials import ( "context" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/db" + "github.com/openshift-online/rh-trex/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/logger" + "github.com/openshift-online/rh-trex/pkg/services" ) type RegistryCredentialService interface { - Get(ctx context.Context, id string) (*api.RegistryCredential, *errors.ServiceError) - Create(ctx context.Context, registryCredential *api.RegistryCredential) (*api.RegistryCredential, *errors.ServiceError) - Replace(ctx context.Context, registryCredential *api.RegistryCredential) (*api.RegistryCredential, *errors.ServiceError) + Get(ctx context.Context, id string) (*RegistryCredential, *errors.ServiceError) + Create(ctx context.Context, registryCredential *RegistryCredential) (*RegistryCredential, *errors.ServiceError) + Replace(ctx context.Context, registryCredential *RegistryCredential) (*RegistryCredential, *errors.ServiceError) Delete(ctx context.Context, id string) *errors.ServiceError - All(ctx context.Context) (api.RegistryCredentialList, *errors.ServiceError) - - FindByIDs(ctx context.Context, ids []string) (api.RegistryCredentialList, *errors.ServiceError) - - // idempotent functions for the control plane, but can also be called synchronously by any actor + All(ctx context.Context) (RegistryCredentialList, *errors.ServiceError) + FindByIDs(ctx context.Context, ids []string) (RegistryCredentialList, *errors.ServiceError) OnUpsert(ctx context.Context, id string) error OnDelete(ctx context.Context, id string) error } -func NewRegistryCredentialService(lockFactory db.LockFactory, registryCredentialDao dao.RegistryCredentialDao, events EventService) RegistryCredentialService { +func NewRegistryCredentialService(lockFactory db.LockFactory, registryCredentialDao RegistryCredentialDao, events services.EventService) RegistryCredentialService { return &sqlRegistryCredentialService{ lockFactory: lockFactory, registryCredentialDao: registryCredentialDao, @@ -36,22 +33,22 @@ var _ RegistryCredentialService = &sqlRegistryCredentialService{} type sqlRegistryCredentialService struct { lockFactory db.LockFactory - registryCredentialDao dao.RegistryCredentialDao - events EventService + registryCredentialDao RegistryCredentialDao + events services.EventService } -func (s *sqlRegistryCredentialService) Get(ctx context.Context, id string) (*api.RegistryCredential, *errors.ServiceError) { +func (s *sqlRegistryCredentialService) Get(ctx context.Context, id string) (*RegistryCredential, *errors.ServiceError) { registryCredential, err := s.registryCredentialDao.Get(ctx, id) if err != nil { - return nil, handleGetError("RegistryCredential", "id", id, err) + return nil, services.HandleGetError("RegistryCredential", "id", id, err) } return registryCredential, nil } -func (s *sqlRegistryCredentialService) Create(ctx context.Context, registryCredential *api.RegistryCredential) (*api.RegistryCredential, *errors.ServiceError) { +func (s *sqlRegistryCredentialService) Create(ctx context.Context, registryCredential *RegistryCredential) (*RegistryCredential, *errors.ServiceError) { registryCredential, err := s.registryCredentialDao.Create(ctx, registryCredential) if err != nil { - return nil, handleCreateError("RegistryCredential", err) + return nil, services.HandleCreateError("RegistryCredential", err) } _, evErr := s.events.Create(ctx, &api.Event{ @@ -60,16 +57,16 @@ func (s *sqlRegistryCredentialService) Create(ctx context.Context, registryCrede EventType: api.CreateEventType, }) if evErr != nil { - return nil, handleCreateError("RegistryCredential", evErr) + return nil, services.HandleCreateError("RegistryCredential", evErr) } return registryCredential, nil } -func (s *sqlRegistryCredentialService) Replace(ctx context.Context, registryCredential *api.RegistryCredential) (*api.RegistryCredential, *errors.ServiceError) { +func (s *sqlRegistryCredentialService) Replace(ctx context.Context, registryCredential *RegistryCredential) (*RegistryCredential, *errors.ServiceError) { registryCredential, err := s.registryCredentialDao.Replace(ctx, registryCredential) if err != nil { - return nil, handleUpdateError("RegistryCredential", err) + return nil, services.HandleUpdateError("RegistryCredential", err) } _, evErr := s.events.Create(ctx, &api.Event{ @@ -78,7 +75,7 @@ func (s *sqlRegistryCredentialService) Replace(ctx context.Context, registryCred EventType: api.UpdateEventType, }) if evErr != nil { - return nil, handleUpdateError("RegistryCredential", evErr) + return nil, services.HandleUpdateError("RegistryCredential", evErr) } return registryCredential, nil @@ -86,7 +83,7 @@ func (s *sqlRegistryCredentialService) Replace(ctx context.Context, registryCred func (s *sqlRegistryCredentialService) Delete(ctx context.Context, id string) *errors.ServiceError { if err := s.registryCredentialDao.Delete(ctx, id); err != nil { - return handleDeleteError("RegistryCredential", errors.GeneralError("Unable to delete registryCredential: %s", err)) + return services.HandleDeleteError("RegistryCredential", errors.GeneralError("Unable to delete registryCredential: %s", err)) } _, evErr := s.events.Create(ctx, &api.Event{ @@ -95,13 +92,13 @@ func (s *sqlRegistryCredentialService) Delete(ctx context.Context, id string) *e EventType: api.DeleteEventType, }) if evErr != nil { - return handleDeleteError("RegistryCredential", evErr) + return services.HandleDeleteError("RegistryCredential", evErr) } return nil } -func (s *sqlRegistryCredentialService) FindByIDs(ctx context.Context, ids []string) (api.RegistryCredentialList, *errors.ServiceError) { +func (s *sqlRegistryCredentialService) FindByIDs(ctx context.Context, ids []string) (RegistryCredentialList, *errors.ServiceError) { registryCredentials, err := s.registryCredentialDao.FindByIDs(ctx, ids) if err != nil { return nil, errors.GeneralError("Unable to get all registryCredentials: %s", err) @@ -109,7 +106,7 @@ func (s *sqlRegistryCredentialService) FindByIDs(ctx context.Context, ids []stri return registryCredentials, nil } -func (s *sqlRegistryCredentialService) All(ctx context.Context) (api.RegistryCredentialList, *errors.ServiceError) { +func (s *sqlRegistryCredentialService) All(ctx context.Context) (RegistryCredentialList, *errors.ServiceError) { registryCredentials, err := s.registryCredentialDao.All(ctx) if err != nil { return nil, errors.GeneralError("Unable to get all registryCredentials: %s", err) diff --git a/pkg/dao/registry.go b/plugins/registrys/dao.go similarity index 58% rename from pkg/dao/registry.go rename to plugins/registrys/dao.go index 83e1574..4bea770 100644 --- a/pkg/dao/registry.go +++ b/plugins/registrys/dao.go @@ -1,21 +1,21 @@ -package dao +package registrys import ( "context" "gorm.io/gorm/clause" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/db" ) type RegistryDao interface { - Get(ctx context.Context, id string) (*api.Registry, error) - Create(ctx context.Context, registry *api.Registry) (*api.Registry, error) - Replace(ctx context.Context, registry *api.Registry) (*api.Registry, error) + Get(ctx context.Context, id string) (*Registry, error) + Create(ctx context.Context, registry *Registry) (*Registry, error) + Replace(ctx context.Context, registry *Registry) (*Registry, error) Delete(ctx context.Context, id string) error - FindByIDs(ctx context.Context, ids []string) (api.RegistryList, error) - All(ctx context.Context) (api.RegistryList, error) + FindByIDs(ctx context.Context, ids []string) (RegistryList, error) + All(ctx context.Context) (RegistryList, error) } var _ RegistryDao = &sqlRegistryDao{} @@ -28,16 +28,16 @@ func NewRegistryDao(sessionFactory *db.SessionFactory) RegistryDao { return &sqlRegistryDao{sessionFactory: sessionFactory} } -func (d *sqlRegistryDao) Get(ctx context.Context, id string) (*api.Registry, error) { +func (d *sqlRegistryDao) Get(ctx context.Context, id string) (*Registry, error) { g2 := (*d.sessionFactory).New(ctx) - var registry api.Registry + var registry Registry if err := g2.Take(®istry, "id = ?", id).Error; err != nil { return nil, err } return ®istry, nil } -func (d *sqlRegistryDao) Create(ctx context.Context, registry *api.Registry) (*api.Registry, error) { +func (d *sqlRegistryDao) Create(ctx context.Context, registry *Registry) (*Registry, error) { g2 := (*d.sessionFactory).New(ctx) if err := g2.Omit(clause.Associations).Create(registry).Error; err != nil { db.MarkForRollback(ctx, err) @@ -46,7 +46,7 @@ func (d *sqlRegistryDao) Create(ctx context.Context, registry *api.Registry) (*a return registry, nil } -func (d *sqlRegistryDao) Replace(ctx context.Context, registry *api.Registry) (*api.Registry, error) { +func (d *sqlRegistryDao) Replace(ctx context.Context, registry *Registry) (*Registry, error) { g2 := (*d.sessionFactory).New(ctx) if err := g2.Omit(clause.Associations).Save(registry).Error; err != nil { db.MarkForRollback(ctx, err) @@ -57,25 +57,25 @@ func (d *sqlRegistryDao) Replace(ctx context.Context, registry *api.Registry) (* func (d *sqlRegistryDao) Delete(ctx context.Context, id string) error { g2 := (*d.sessionFactory).New(ctx) - if err := g2.Omit(clause.Associations).Delete(&api.Registry{Meta: api.Meta{ID: id}}).Error; err != nil { + if err := g2.Omit(clause.Associations).Delete(&Registry{Meta: api.Meta{ID: id}}).Error; err != nil { db.MarkForRollback(ctx, err) return err } return nil } -func (d *sqlRegistryDao) FindByIDs(ctx context.Context, ids []string) (api.RegistryList, error) { +func (d *sqlRegistryDao) FindByIDs(ctx context.Context, ids []string) (RegistryList, error) { g2 := (*d.sessionFactory).New(ctx) - registrys := api.RegistryList{} + registrys := RegistryList{} if err := g2.Where("id in (?)", ids).Find(®istrys).Error; err != nil { return nil, err } return registrys, nil } -func (d *sqlRegistryDao) All(ctx context.Context) (api.RegistryList, error) { +func (d *sqlRegistryDao) All(ctx context.Context) (RegistryList, error) { g2 := (*d.sessionFactory).New(ctx) - registrys := api.RegistryList{} + registrys := RegistryList{} if err := g2.Find(®istrys).Error; err != nil { return nil, err } diff --git a/pkg/handlers/registry.go b/plugins/registrys/handler.go similarity index 62% rename from pkg/handlers/registry.go rename to plugins/registrys/handler.go index c9803d6..c4da29c 100644 --- a/pkg/handlers/registry.go +++ b/plugins/registrys/handler.go @@ -1,25 +1,23 @@ -package handlers +package registrys import ( "net/http" "github.com/gorilla/mux" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/presenters" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" + "github.com/openshift-online/rh-trex/pkg/api/presenters" + "github.com/openshift-online/rh-trex/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/handlers" + "github.com/openshift-online/rh-trex/pkg/services" ) -var _ RestHandler = registryHandler{} - type registryHandler struct { - registry services.RegistryService + registry RegistryService generic services.GenericService } -func NewRegistryHandler(registry services.RegistryService, generic services.GenericService) *registryHandler { +func NewRegistryHandler(registry RegistryService, generic services.GenericService) *registryHandler { return ®istryHandler{ registry: registry, generic: generic, @@ -28,33 +26,33 @@ func NewRegistryHandler(registry services.RegistryService, generic services.Gene func (h registryHandler) Create(w http.ResponseWriter, r *http.Request) { var registry openapi.Registry - cfg := &handlerConfig{ - ®istry, - []validate{ - validateEmpty(®istry, "Id", "id"), + cfg := &handlers.HandlerConfig{ + Body: ®istry, + Validators: []handlers.Validate{ + handlers.ValidateEmpty(®istry, "Id", "id"), }, - func() (interface{}, *errors.ServiceError) { + Action: func() (interface{}, *errors.ServiceError) { ctx := r.Context() - registryModel := presenters.ConvertRegistry(registry) + registryModel := ConvertRegistry(registry) registryModel, err := h.registry.Create(ctx, registryModel) if err != nil { return nil, err } - return presenters.PresentRegistry(registryModel), nil + return PresentRegistry(registryModel), nil }, - handleError, + ErrorHandler: handlers.HandleError, } - handle(w, r, cfg, http.StatusCreated) + handlers.Handle(w, r, cfg, http.StatusCreated) } func (h registryHandler) Patch(w http.ResponseWriter, r *http.Request) { var patch openapi.RegistryPatchRequest - cfg := &handlerConfig{ - &patch, - []validate{}, - func() (interface{}, *errors.ServiceError) { + cfg := &handlers.HandlerConfig{ + Body: &patch, + Validators: []handlers.Validate{}, + Action: func() (interface{}, *errors.ServiceError) { ctx := r.Context() id := mux.Vars(r)["id"] found, err := h.registry.Get(ctx, id) @@ -62,27 +60,25 @@ func (h registryHandler) Patch(w http.ResponseWriter, r *http.Request) { return nil, err } - //patch a field - registryModel, err := h.registry.Replace(ctx, found) if err != nil { return nil, err } - return presenters.PresentRegistry(registryModel), nil + return PresentRegistry(registryModel), nil }, - handleError, + ErrorHandler: handlers.HandleError, } - handle(w, r, cfg, http.StatusOK) + handlers.Handle(w, r, cfg, http.StatusOK) } func (h registryHandler) List(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ + cfg := &handlers.HandlerConfig{ Action: func() (interface{}, *errors.ServiceError) { ctx := r.Context() listArgs := services.NewListArguments(r.URL.Query()) - var registrys []api.Registry + var registrys []Registry paging, err := h.generic.List(ctx, "username", listArgs, ®istrys) if err != nil { return nil, err @@ -96,7 +92,7 @@ func (h registryHandler) List(w http.ResponseWriter, r *http.Request) { } for _, registry := range registrys { - converted := presenters.PresentRegistry(®istry) + converted := PresentRegistry(®istry) registryList.Items = append(registryList.Items, converted) } if listArgs.Fields != nil { @@ -110,11 +106,11 @@ func (h registryHandler) List(w http.ResponseWriter, r *http.Request) { }, } - handleList(w, r, cfg) + handlers.HandleList(w, r, cfg) } func (h registryHandler) Get(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ + cfg := &handlers.HandlerConfig{ Action: func() (interface{}, *errors.ServiceError) { id := mux.Vars(r)["id"] ctx := r.Context() @@ -123,18 +119,18 @@ func (h registryHandler) Get(w http.ResponseWriter, r *http.Request) { return nil, err } - return presenters.PresentRegistry(registry), nil + return PresentRegistry(registry), nil }, } - handleGet(w, r, cfg) + handlers.HandleGet(w, r, cfg) } func (h registryHandler) Delete(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ + cfg := &handlers.HandlerConfig{ Action: func() (interface{}, *errors.ServiceError) { return nil, errors.NotImplemented("delete") }, } - handleDelete(w, r, cfg, http.StatusNoContent) + handlers.HandleDelete(w, r, cfg, http.StatusNoContent) } diff --git a/pkg/db/migrations/202511211537_add_registrys.go b/plugins/registrys/migration.go similarity index 73% rename from pkg/db/migrations/202511211537_add_registrys.go rename to plugins/registrys/migration.go index a0535ee..57c18b5 100644 --- a/pkg/db/migrations/202511211537_add_registrys.go +++ b/plugins/registrys/migration.go @@ -1,14 +1,14 @@ -package migrations +package registrys import ( - "gorm.io/gorm" - "github.com/go-gormigrate/gormigrate/v2" + "github.com/openshift-online/rh-trex/pkg/db" + "gorm.io/gorm" ) -func addRegistrys() *gormigrate.Migration { +func migration() *gormigrate.Migration { type Registry struct { - Model + db.Model } return &gormigrate.Migration{ diff --git a/pkg/dao/mocks/registry.go b/plugins/registrys/mock_dao.go similarity index 60% rename from pkg/dao/mocks/registry.go rename to plugins/registrys/mock_dao.go index 351cc60..63e72e3 100644 --- a/pkg/dao/mocks/registry.go +++ b/plugins/registrys/mock_dao.go @@ -1,26 +1,24 @@ -package mocks +package registrys import ( "context" "gorm.io/gorm" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/errors" ) -var _ dao.RegistryDao = ®istryDaoMock{} +var _ RegistryDao = ®istryDaoMock{} type registryDaoMock struct { - registrys api.RegistryList + registrys RegistryList } -func NewRegistryDao() *registryDaoMock { +func NewMockRegistryDao() *registryDaoMock { return ®istryDaoMock{} } -func (d *registryDaoMock) Get(ctx context.Context, id string) (*api.Registry, error) { +func (d *registryDaoMock) Get(ctx context.Context, id string) (*Registry, error) { for _, registry := range d.registrys { if registry.ID == id { return registry, nil @@ -29,12 +27,12 @@ func (d *registryDaoMock) Get(ctx context.Context, id string) (*api.Registry, er return nil, gorm.ErrRecordNotFound } -func (d *registryDaoMock) Create(ctx context.Context, registry *api.Registry) (*api.Registry, error) { +func (d *registryDaoMock) Create(ctx context.Context, registry *Registry) (*Registry, error) { d.registrys = append(d.registrys, registry) return registry, nil } -func (d *registryDaoMock) Replace(ctx context.Context, registry *api.Registry) (*api.Registry, error) { +func (d *registryDaoMock) Replace(ctx context.Context, registry *Registry) (*Registry, error) { return nil, errors.NotImplemented("Registry").AsError() } @@ -42,10 +40,10 @@ func (d *registryDaoMock) Delete(ctx context.Context, id string) error { return errors.NotImplemented("Registry").AsError() } -func (d *registryDaoMock) FindByIDs(ctx context.Context, ids []string) (api.RegistryList, error) { +func (d *registryDaoMock) FindByIDs(ctx context.Context, ids []string) (RegistryList, error) { return nil, errors.NotImplemented("Registry").AsError() } -func (d *registryDaoMock) All(ctx context.Context) (api.RegistryList, error) { +func (d *registryDaoMock) All(ctx context.Context) (RegistryList, error) { return d.registrys, nil } diff --git a/pkg/api/registry.go b/plugins/registrys/model.go similarity index 73% rename from pkg/api/registry.go rename to plugins/registrys/model.go index 21f2271..d17ec65 100644 --- a/pkg/api/registry.go +++ b/plugins/registrys/model.go @@ -1,12 +1,16 @@ -package api +package registrys -import "gorm.io/gorm" +import ( + "github.com/openshift-online/rh-trex/pkg/api" + "gorm.io/gorm" +) type Registry struct { - Meta + api.Meta } type RegistryList []*Registry + type RegistryIndex map[string]*Registry func (l RegistryList) Index() RegistryIndex { @@ -18,7 +22,7 @@ func (l RegistryList) Index() RegistryIndex { } func (d *Registry) BeforeCreate(tx *gorm.DB) error { - d.ID = NewID() + d.ID = api.NewID() return nil } diff --git a/plugins/registrys/plugin.go b/plugins/registrys/plugin.go index 4754dfa..6718116 100644 --- a/plugins/registrys/plugin.go +++ b/plugins/registrys/plugin.go @@ -5,35 +5,30 @@ import ( "github.com/gorilla/mux" "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments/registry" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/server" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/presenters" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/auth" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/controllers" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/handlers" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" - "github.com/openshift-hyperfleet/registry-credentials-service/plugins/events" - "github.com/openshift-hyperfleet/registry-credentials-service/plugins/generic" + "github.com/openshift-online/rh-trex/plugins/events" + "github.com/openshift-online/rh-trex/plugins/generic" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/api/presenters" + "github.com/openshift-online/rh-trex/pkg/auth" + "github.com/openshift-online/rh-trex/pkg/controllers" + "github.com/openshift-online/rh-trex/pkg/db" + pkgregistry "github.com/openshift-online/rh-trex/pkg/registry" + pkgserver "github.com/openshift-online/rh-trex/pkg/server" ) -// ServiceLocator Service Locator -type ServiceLocator func() services.RegistryService +type ServiceLocator func() RegistryService func NewServiceLocator(env *environments.Env) ServiceLocator { - return func() services.RegistryService { - return services.NewRegistryService( + return func() RegistryService { + return NewRegistryService( db.NewAdvisoryLockFactory(env.Database.SessionFactory), - dao.NewRegistryDao(&env.Database.SessionFactory), + NewRegistryDao(&env.Database.SessionFactory), events.Service(&env.Services), ) } } -// Service helper function to get the registry service from the registry -func Service(s *environments.Services) services.RegistryService { +func Service(s *environments.Services) RegistryService { if s == nil { return nil } @@ -45,15 +40,15 @@ func Service(s *environments.Services) services.RegistryService { } func init() { - // Service registration - registry.RegisterService("Registrys", func(env interface{}) interface{} { + db.RegisterMigration(migration()) + + pkgregistry.RegisterService("Registrys", func(env interface{}) interface{} { return NewServiceLocator(env.(*environments.Env)) }) - // Routes registration - server.RegisterRoutes("registrys", func(apiV1Router *mux.Router, services server.ServicesInterface, authMiddleware auth.JWTMiddleware, authzMiddleware auth.AuthorizationMiddleware) { + pkgserver.RegisterRoutes("registrys", func(apiV1Router *mux.Router, services pkgserver.ServicesInterface, authMiddleware auth.JWTMiddleware, authzMiddleware auth.AuthorizationMiddleware) { envServices := services.(*environments.Services) - registryHandler := handlers.NewRegistryHandler(Service(envServices), generic.Service(envServices)) + registryHandler := NewRegistryHandler(Service(envServices), generic.Service(envServices)) registrysRouter := apiV1Router.PathPrefix("/registrys").Subrouter() registrysRouter.HandleFunc("", registryHandler.List).Methods(http.MethodGet) @@ -65,9 +60,9 @@ func init() { registrysRouter.Use(authzMiddleware.AuthorizeApi) }) - // Controller registration - server.RegisterController("Registrys", func(manager *controllers.KindControllerManager, services *environments.Services) { - registryServices := Service(services) + pkgserver.RegisterController("Registrys", func(manager *controllers.KindControllerManager, services pkgserver.ServicesInterface) { + envServices := services.(*environments.Services) + registryServices := Service(envServices) manager.Add(&controllers.ControllerConfig{ Source: "Registrys", @@ -79,9 +74,8 @@ func init() { }) }) - // Presenter registration - presenters.RegisterPath(api.Registry{}, "registrys") - presenters.RegisterPath(&api.Registry{}, "registrys") - presenters.RegisterKind(api.Registry{}, "Registry") - presenters.RegisterKind(&api.Registry{}, "Registry") + presenters.RegisterPath(Registry{}, "registrys") + presenters.RegisterPath(&Registry{}, "registrys") + presenters.RegisterKind(Registry{}, "Registry") + presenters.RegisterKind(&Registry{}, "Registry") } diff --git a/plugins/registrys/presenter.go b/plugins/registrys/presenter.go new file mode 100644 index 0000000..7cf4df2 --- /dev/null +++ b/plugins/registrys/presenter.go @@ -0,0 +1,30 @@ +package registrys + +import ( + "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" + "github.com/openshift-online/rh-trex/pkg/api/presenters" + "github.com/openshift-online/rh-trex/pkg/util" +) + +func ConvertRegistry(registry openapi.Registry) *Registry { + c := &Registry{} + c.ID = util.NilToEmptyString(registry.Id) + + if registry.CreatedAt != nil { + c.CreatedAt = *registry.CreatedAt + c.UpdatedAt = *registry.UpdatedAt + } + + return c +} + +func PresentRegistry(registry *Registry) openapi.Registry { + reference := presenters.PresentReference(registry.ID, registry) + return openapi.Registry{ + Id: reference.Id, + Kind: reference.Kind, + Href: reference.Href, + CreatedAt: openapi.PtrTime(registry.CreatedAt), + UpdatedAt: openapi.PtrTime(registry.UpdatedAt), + } +} diff --git a/pkg/services/registry.go b/plugins/registrys/service.go similarity index 59% rename from pkg/services/registry.go rename to plugins/registrys/service.go index 21d67fc..ec365fb 100644 --- a/pkg/services/registry.go +++ b/plugins/registrys/service.go @@ -1,30 +1,27 @@ -package services +package registrys import ( "context" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/logger" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/db" + "github.com/openshift-online/rh-trex/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/logger" + "github.com/openshift-online/rh-trex/pkg/services" ) type RegistryService interface { - Get(ctx context.Context, id string) (*api.Registry, *errors.ServiceError) - Create(ctx context.Context, registry *api.Registry) (*api.Registry, *errors.ServiceError) - Replace(ctx context.Context, registry *api.Registry) (*api.Registry, *errors.ServiceError) + Get(ctx context.Context, id string) (*Registry, *errors.ServiceError) + Create(ctx context.Context, registry *Registry) (*Registry, *errors.ServiceError) + Replace(ctx context.Context, registry *Registry) (*Registry, *errors.ServiceError) Delete(ctx context.Context, id string) *errors.ServiceError - All(ctx context.Context) (api.RegistryList, *errors.ServiceError) - - FindByIDs(ctx context.Context, ids []string) (api.RegistryList, *errors.ServiceError) - - // idempotent functions for the control plane, but can also be called synchronously by any actor + All(ctx context.Context) (RegistryList, *errors.ServiceError) + FindByIDs(ctx context.Context, ids []string) (RegistryList, *errors.ServiceError) OnUpsert(ctx context.Context, id string) error OnDelete(ctx context.Context, id string) error } -func NewRegistryService(lockFactory db.LockFactory, registryDao dao.RegistryDao, events EventService) RegistryService { +func NewRegistryService(lockFactory db.LockFactory, registryDao RegistryDao, events services.EventService) RegistryService { return &sqlRegistryService{ lockFactory: lockFactory, registryDao: registryDao, @@ -36,22 +33,22 @@ var _ RegistryService = &sqlRegistryService{} type sqlRegistryService struct { lockFactory db.LockFactory - registryDao dao.RegistryDao - events EventService + registryDao RegistryDao + events services.EventService } -func (s *sqlRegistryService) Get(ctx context.Context, id string) (*api.Registry, *errors.ServiceError) { +func (s *sqlRegistryService) Get(ctx context.Context, id string) (*Registry, *errors.ServiceError) { registry, err := s.registryDao.Get(ctx, id) if err != nil { - return nil, handleGetError("Registry", "id", id, err) + return nil, services.HandleGetError("Registry", "id", id, err) } return registry, nil } -func (s *sqlRegistryService) Create(ctx context.Context, registry *api.Registry) (*api.Registry, *errors.ServiceError) { +func (s *sqlRegistryService) Create(ctx context.Context, registry *Registry) (*Registry, *errors.ServiceError) { registry, err := s.registryDao.Create(ctx, registry) if err != nil { - return nil, handleCreateError("Registry", err) + return nil, services.HandleCreateError("Registry", err) } _, evErr := s.events.Create(ctx, &api.Event{ @@ -60,16 +57,16 @@ func (s *sqlRegistryService) Create(ctx context.Context, registry *api.Registry) EventType: api.CreateEventType, }) if evErr != nil { - return nil, handleCreateError("Registry", evErr) + return nil, services.HandleCreateError("Registry", evErr) } return registry, nil } -func (s *sqlRegistryService) Replace(ctx context.Context, registry *api.Registry) (*api.Registry, *errors.ServiceError) { +func (s *sqlRegistryService) Replace(ctx context.Context, registry *Registry) (*Registry, *errors.ServiceError) { registry, err := s.registryDao.Replace(ctx, registry) if err != nil { - return nil, handleUpdateError("Registry", err) + return nil, services.HandleUpdateError("Registry", err) } _, evErr := s.events.Create(ctx, &api.Event{ @@ -78,7 +75,7 @@ func (s *sqlRegistryService) Replace(ctx context.Context, registry *api.Registry EventType: api.UpdateEventType, }) if evErr != nil { - return nil, handleUpdateError("Registry", evErr) + return nil, services.HandleUpdateError("Registry", evErr) } return registry, nil @@ -86,7 +83,7 @@ func (s *sqlRegistryService) Replace(ctx context.Context, registry *api.Registry func (s *sqlRegistryService) Delete(ctx context.Context, id string) *errors.ServiceError { if err := s.registryDao.Delete(ctx, id); err != nil { - return handleDeleteError("Registry", errors.GeneralError("Unable to delete registry: %s", err)) + return services.HandleDeleteError("Registry", errors.GeneralError("Unable to delete registry: %s", err)) } _, evErr := s.events.Create(ctx, &api.Event{ @@ -95,13 +92,13 @@ func (s *sqlRegistryService) Delete(ctx context.Context, id string) *errors.Serv EventType: api.DeleteEventType, }) if evErr != nil { - return handleDeleteError("Registry", evErr) + return services.HandleDeleteError("Registry", evErr) } return nil } -func (s *sqlRegistryService) FindByIDs(ctx context.Context, ids []string) (api.RegistryList, *errors.ServiceError) { +func (s *sqlRegistryService) FindByIDs(ctx context.Context, ids []string) (RegistryList, *errors.ServiceError) { registrys, err := s.registryDao.FindByIDs(ctx, ids) if err != nil { return nil, errors.GeneralError("Unable to get all registrys: %s", err) @@ -109,7 +106,7 @@ func (s *sqlRegistryService) FindByIDs(ctx context.Context, ids []string) (api.R return registrys, nil } -func (s *sqlRegistryService) All(ctx context.Context) (api.RegistryList, *errors.ServiceError) { +func (s *sqlRegistryService) All(ctx context.Context) (RegistryList, *errors.ServiceError) { registrys, err := s.registryDao.All(ctx) if err != nil { return nil, errors.GeneralError("Unable to get all registrys: %s", err) diff --git a/scripts/generator.go b/scripts/generator.go index 19b1756..ab4b7c3 100755 --- a/scripts/generator.go +++ b/scripts/generator.go @@ -30,7 +30,7 @@ TODO: all of it can be better var ( kind = "Asteroid" repo = "github.com/openshift-online" - project = "registry-credential-service" + project = "registry-credentials-service" fields = "" openapiEndpointStart = "# NEW ENDPOINT START" openapiEndpointEnd = "# NEW ENDPOINT END" @@ -47,7 +47,7 @@ func init() { flags.StringVar(&kind, "kind", kind, "the name of the kind. e.g Account or User") flags.StringVar(&repo, "repo", repo, "the name of the repo. e.g github.com/yourproject") - flags.StringVar(&project, "project", project, "the name of the project. e.g registry-credential-service") + flags.StringVar(&project, "project", project, "the name of the project. e.g registry-credentials-service") flags.StringVar(&fields, "fields", fields, "comma-separated list of custom fields in format name:type (e.g. 'name:string,age:int,active:bool')") } @@ -85,6 +85,7 @@ func main() { "migration", "test", "test-factories", + "testmain", "handlers", "openapi-kind", "plugin", @@ -106,6 +107,7 @@ func main() { kindSnakeCase := toSnakeCase(kind) k := myWriter{ Project: project, + ProjectPascalCase: toPascalCase(project), Repo: repo, Cmd: getCmdDir(), Kind: kind, @@ -120,16 +122,17 @@ func main() { k.ID = fmt.Sprintf("%d%s%s%s%s", now.Year(), datePad(int(now.Month())), datePad(now.Day()), datePad(now.Hour()), datePad(now.Minute())) outputPaths := map[string]string{ - "generate-api": fmt.Sprintf("pkg/%s/%s.go", nm, k.KindLowerSingular), - "generate-presenters": fmt.Sprintf("pkg/api/presenters/%s.go", k.KindLowerSingular), - "generate-dao": fmt.Sprintf("pkg/%s/%s.go", nm, k.KindLowerSingular), - "generate-handlers": fmt.Sprintf("pkg/%s/%s.go", nm, k.KindLowerSingular), - "generate-migration": fmt.Sprintf("pkg/db/migrations/%s_add_%s.go", k.ID, k.KindLowerPlural), - "generate-mock": fmt.Sprintf("pkg/dao/mocks/%s.go", k.KindLowerSingular), + "generate-api": fmt.Sprintf("plugins/%s/model.go", k.KindLowerPlural), + "generate-presenters": fmt.Sprintf("plugins/%s/presenter.go", k.KindLowerPlural), + "generate-dao": fmt.Sprintf("plugins/%s/dao.go", k.KindLowerPlural), + "generate-handlers": fmt.Sprintf("plugins/%s/handler.go", k.KindLowerPlural), + "generate-migration": fmt.Sprintf("plugins/%s/migration.go", k.KindLowerPlural), + "generate-mock": fmt.Sprintf("plugins/%s/mock_dao.go", k.KindLowerPlural), "generate-openapi-kind": fmt.Sprintf("openapi/openapi.%s.yaml", k.KindLowerPlural), - "generate-test-factories": fmt.Sprintf("test/factories/%s.go", k.KindLowerPlural), - "generate-test": fmt.Sprintf("test/integration/%s_test.go", k.KindLowerPlural), - "generate-services": fmt.Sprintf("pkg/%s/%s.go", nm, k.KindLowerSingular), + "generate-test-factories": fmt.Sprintf("plugins/%s/factory_test.go", k.KindLowerPlural), + "generate-test": fmt.Sprintf("plugins/%s/integration_test.go", k.KindLowerPlural), + "generate-testmain": fmt.Sprintf("plugins/%s/testmain_test.go", k.KindLowerPlural), + "generate-services": fmt.Sprintf("plugins/%s/service.go", k.KindLowerPlural), "generate-plugin": fmt.Sprintf("plugins/%s/plugin.go", k.KindLowerPlural), } @@ -176,10 +179,6 @@ func main() { addPluginImport(k) } - // Add migration to migration_structs.go after generating the migration - if nm == "migration" { - addMigrationToList(k) - } } // Run make generate to regenerate OpenAPI client @@ -303,6 +302,7 @@ func mapFieldType(name, fieldType string, nullable bool) (Field, error) { field.DBType = "integer" field.OpenAPIType = "integer" field.OpenAPIFormat = "int32" + field.NeedsIntConversion = true case "int64": baseType = "int64" pointerType = "*int64" @@ -342,24 +342,26 @@ func mapFieldType(name, fieldType string, nullable bool) (Field, error) { } type Field struct { - Name string - Type string - GoType string - DBType string - OpenAPIType string - OpenAPIFormat string - NameSnakeCase string - NameCamelCase string - JSONTag string - GormTag string - Required bool - Nullable bool - PointerType string + Name string + Type string + GoType string + DBType string + OpenAPIType string + OpenAPIFormat string + NameSnakeCase string + NameCamelCase string + JSONTag string + GormTag string + Required bool + Nullable bool + PointerType string + NeedsIntConversion bool } type myWriter struct { Repo string Project string + ProjectPascalCase string Cmd string Kind string KindPlural string @@ -478,51 +480,3 @@ func addPluginImport(k myWriter) { panic("Could not find import block in " + mainFile) } -func addMigrationToList(k myWriter) { - migrationFile := "pkg/db/migrations/migration_structs.go" - - input, err := os.ReadFile(migrationFile) - if err != nil { - panic(err) - } - - migrationFunc := fmt.Sprintf("add%s()", k.KindPlural) - - // Check if the migration already exists in the list - if strings.Contains(string(input), migrationFunc) { - fmt.Printf("Migration '%s' already exists in MigrationList in %s\n", migrationFunc, migrationFile) - return - } - - // Find the MigrationList and add the new migration before the closing brace - migrationListStart := "var MigrationList = []*gormigrate.Migration{" - lines := strings.Split(string(input), "\n") - var output []string - - for i, line := range lines { - if strings.Contains(line, migrationListStart) { - // Found the start, copy everything until closing brace - output = append(output, line) - for j := i + 1; j < len(lines); j++ { - if strings.TrimSpace(lines[j]) == "}" { - // Insert new migration before closing brace - output = append(output, fmt.Sprintf("\t%s,", migrationFunc)) - output = append(output, lines[j]) - // Copy remaining lines - output = append(output, lines[j+1:]...) - - err = os.WriteFile(migrationFile, []byte(strings.Join(output, "\n")), 0666) - if err != nil { - panic(err) - } - fmt.Printf("Added migration '%s' to MigrationList in %s\n", migrationFunc, migrationFile) - return - } - output = append(output, lines[j]) - } - } - output = append(output, line) - } - - panic("Could not find MigrationList in " + migrationFile) -} diff --git a/templates/generate-api.txt b/templates/generate-api.txt index 4545eb6..9b6ed7f 100755 --- a/templates/generate-api.txt +++ b/templates/generate-api.txt @@ -1,9 +1,13 @@ -package api +package {{.KindLowerPlural}} -import "gorm.io/gorm" +import ( + "gorm.io/gorm" + + "github.com/openshift-online/rh-trex/pkg/api" +) type {{.Kind}} struct { - Meta + api.Meta {{- range .Fields}} {{.Name}} {{.GoType}} {{.JSONTag}} {{- end}} @@ -21,7 +25,7 @@ func (l {{.Kind}}List) Index() {{.Kind}}Index { } func (d *{{.Kind}}) BeforeCreate(tx *gorm.DB) error { - d.ID = NewID() + d.ID = api.NewID() return nil } diff --git a/templates/generate-dao.txt b/templates/generate-dao.txt index 072658b..b52c3c4 100755 --- a/templates/generate-dao.txt +++ b/templates/generate-dao.txt @@ -1,21 +1,21 @@ -package dao +package {{.KindLowerPlural}} import ( "context" "gorm.io/gorm/clause" - "{{.Repo}}/{{.Project}}/pkg/api" - "{{.Repo}}/{{.Project}}/pkg/db" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/db" ) type {{.Kind}}Dao interface { - Get(ctx context.Context, id string) (*api.{{.Kind}}, error) - Create(ctx context.Context, {{.KindLowerSingular}} *api.{{.Kind}}) (*api.{{.Kind}}, error) - Replace(ctx context.Context, {{.KindLowerSingular}} *api.{{.Kind}}) (*api.{{.Kind}}, error) + Get(ctx context.Context, id string) (*{{.Kind}}, error) + Create(ctx context.Context, {{.KindLowerSingular}} *{{.Kind}}) (*{{.Kind}}, error) + Replace(ctx context.Context, {{.KindLowerSingular}} *{{.Kind}}) (*{{.Kind}}, error) Delete(ctx context.Context, id string) error - FindByIDs(ctx context.Context, ids []string) (api.{{.Kind}}List, error) - All(ctx context.Context) (api.{{.Kind}}List, error) + FindByIDs(ctx context.Context, ids []string) ({{.Kind}}List, error) + All(ctx context.Context) ({{.Kind}}List, error) } var _ {{.Kind}}Dao = &sql{{.Kind}}Dao{} @@ -28,16 +28,16 @@ func New{{.Kind}}Dao(sessionFactory *db.SessionFactory) {{.Kind}}Dao { return &sql{{.Kind}}Dao{sessionFactory: sessionFactory} } -func (d *sql{{.Kind}}Dao) Get(ctx context.Context, id string) (*api.{{.Kind}}, error) { +func (d *sql{{.Kind}}Dao) Get(ctx context.Context, id string) (*{{.Kind}}, error) { g2 := (*d.sessionFactory).New(ctx) - var {{.KindLowerSingular}} api.{{.Kind}} + var {{.KindLowerSingular}} {{.Kind}} if err := g2.Take(&{{.KindLowerSingular}}, "id = ?", id).Error; err != nil { return nil, err } return &{{.KindLowerSingular}}, nil } -func (d *sql{{.Kind}}Dao) Create(ctx context.Context, {{.KindLowerSingular}} *api.{{.Kind}}) (*api.{{.Kind}}, error) { +func (d *sql{{.Kind}}Dao) Create(ctx context.Context, {{.KindLowerSingular}} *{{.Kind}}) (*{{.Kind}}, error) { g2 := (*d.sessionFactory).New(ctx) if err := g2.Omit(clause.Associations).Create({{.KindLowerSingular}}).Error; err != nil { db.MarkForRollback(ctx, err) @@ -46,7 +46,7 @@ func (d *sql{{.Kind}}Dao) Create(ctx context.Context, {{.KindLowerSingular}} *ap return {{.KindLowerSingular}}, nil } -func (d *sql{{.Kind}}Dao) Replace(ctx context.Context, {{.KindLowerSingular}} *api.{{.Kind}}) (*api.{{.Kind}}, error) { +func (d *sql{{.Kind}}Dao) Replace(ctx context.Context, {{.KindLowerSingular}} *{{.Kind}}) (*{{.Kind}}, error) { g2 := (*d.sessionFactory).New(ctx) if err := g2.Omit(clause.Associations).Save({{.KindLowerSingular}}).Error; err != nil { db.MarkForRollback(ctx, err) @@ -57,25 +57,25 @@ func (d *sql{{.Kind}}Dao) Replace(ctx context.Context, {{.KindLowerSingular}} *a func (d *sql{{.Kind}}Dao) Delete(ctx context.Context, id string) error { g2 := (*d.sessionFactory).New(ctx) - if err := g2.Omit(clause.Associations).Delete(&api.{{.Kind}}{Meta: api.Meta{ID: id}}).Error; err != nil { + if err := g2.Omit(clause.Associations).Delete(&{{.Kind}}{Meta: api.Meta{ID: id}}).Error; err != nil { db.MarkForRollback(ctx, err) return err } return nil } -func (d *sql{{.Kind}}Dao) FindByIDs(ctx context.Context, ids []string) (api.{{.Kind}}List, error) { +func (d *sql{{.Kind}}Dao) FindByIDs(ctx context.Context, ids []string) ({{.Kind}}List, error) { g2 := (*d.sessionFactory).New(ctx) - {{.KindLowerPlural}} := api.{{.Kind}}List{} + {{.KindLowerPlural}} := {{.Kind}}List{} if err := g2.Where("id in (?)", ids).Find(&{{.KindLowerPlural}}).Error; err != nil { return nil, err } return {{.KindLowerPlural}}, nil } -func (d *sql{{.Kind}}Dao) All(ctx context.Context) (api.{{.Kind}}List, error) { +func (d *sql{{.Kind}}Dao) All(ctx context.Context) ({{.Kind}}List, error) { g2 := (*d.sessionFactory).New(ctx) - {{.KindLowerPlural}} := api.{{.Kind}}List{} + {{.KindLowerPlural}} := {{.Kind}}List{} if err := g2.Find(&{{.KindLowerPlural}}).Error; err != nil { return nil, err } diff --git a/templates/generate-handlers.txt b/templates/generate-handlers.txt index 94dc798..a3bc14e 100755 --- a/templates/generate-handlers.txt +++ b/templates/generate-handlers.txt @@ -1,25 +1,25 @@ -package handlers +package {{.KindLowerPlural}} import ( "net/http" "github.com/gorilla/mux" - "{{.Repo}}/{{.Project}}/pkg/api" "{{.Repo}}/{{.Project}}/pkg/api/openapi" - "{{.Repo}}/{{.Project}}/pkg/api/presenters" - "{{.Repo}}/{{.Project}}/pkg/errors" - "{{.Repo}}/{{.Project}}/pkg/services" + "github.com/openshift-online/rh-trex/pkg/api/presenters" + "github.com/openshift-online/rh-trex/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/handlers" + "github.com/openshift-online/rh-trex/pkg/services" ) -var _ RestHandler = {{.KindLowerSingular}}Handler{} +var _ handlers.RestHandler = {{.KindLowerSingular}}Handler{} type {{.KindLowerSingular}}Handler struct { - {{.KindLowerSingular}} services.{{.Kind}}Service + {{.KindLowerSingular}} {{.Kind}}Service generic services.GenericService } -func New{{.Kind}}Handler({{.KindLowerSingular}} services.{{.Kind}}Service, generic services.GenericService) *{{.KindLowerSingular}}Handler { +func New{{.Kind}}Handler({{.KindLowerSingular}} {{.Kind}}Service, generic services.GenericService) *{{.KindLowerSingular}}Handler { return &{{.KindLowerSingular}}Handler{ {{.KindLowerSingular}}: {{.KindLowerSingular}}, generic: generic, @@ -28,33 +28,33 @@ func New{{.Kind}}Handler({{.KindLowerSingular}} services.{{.Kind}}Service, gener func (h {{.KindLowerSingular}}Handler) Create(w http.ResponseWriter, r *http.Request) { var {{.KindLowerSingular}} openapi.{{.Kind}} - cfg := &handlerConfig{ - &{{.KindLowerSingular}}, - []validate{ - validateEmpty(&{{.KindLowerSingular}}, "Id", "id"), + cfg := &handlers.HandlerConfig{ + Body: &{{.KindLowerSingular}}, + Validators: []handlers.Validate{ + handlers.ValidateEmpty(&{{.KindLowerSingular}}, "Id", "id"), }, - func() (interface{}, *errors.ServiceError) { + Action: func() (interface{}, *errors.ServiceError) { ctx := r.Context() - {{.KindLowerSingular}}Model := presenters.Convert{{.Kind}}({{.KindLowerSingular}}) + {{.KindLowerSingular}}Model := Convert{{.Kind}}({{.KindLowerSingular}}) {{.KindLowerSingular}}Model, err := h.{{.KindLowerSingular}}.Create(ctx, {{.KindLowerSingular}}Model) if err != nil { return nil, err } - return presenters.Present{{.Kind}}({{.KindLowerSingular}}Model), nil + return Present{{.Kind}}({{.KindLowerSingular}}Model), nil }, - handleError, + ErrorHandler: handlers.HandleError, } - handle(w, r, cfg, http.StatusCreated) + handlers.Handle(w, r, cfg, http.StatusCreated) } func (h {{.KindLowerSingular}}Handler) Patch(w http.ResponseWriter, r *http.Request) { var patch openapi.{{.Kind}}PatchRequest - cfg := &handlerConfig{ - &patch, - []validate{}, - func() (interface{}, *errors.ServiceError) { + cfg := &handlers.HandlerConfig{ + Body: &patch, + Validators: []handlers.Validate{}, + Action: func() (interface{}, *errors.ServiceError) { ctx := r.Context() id := mux.Vars(r)["id"] found, err := h.{{.KindLowerSingular}}.Get(ctx, id) @@ -62,30 +62,38 @@ func (h {{.KindLowerSingular}}Handler) Patch(w http.ResponseWriter, r *http.Requ return nil, err } - //patch a field{{range .Fields}} - if patch.{{.Name}} != nil { +{{range .Fields}} if patch.{{.Name}} != nil { +{{- if and .NeedsIntConversion .Required}} + found.{{.Name}} = int(*patch.{{.Name}}) +{{- else if and .NeedsIntConversion .Nullable}} + {{.NameCamelCase}}Val := int(*patch.{{.Name}}) + found.{{.Name}} = &{{.NameCamelCase}}Val +{{- else if .Required}} + found.{{.Name}} = *patch.{{.Name}} +{{- else}} found.{{.Name}} = patch.{{.Name}} - }{{end}} - +{{- end}} + } +{{end}} {{.KindLowerSingular}}Model, err := h.{{.KindLowerSingular}}.Replace(ctx, found) if err != nil { return nil, err } - return presenters.Present{{.Kind}}({{.KindLowerSingular}}Model), nil + return Present{{.Kind}}({{.KindLowerSingular}}Model), nil }, - handleError, + ErrorHandler: handlers.HandleError, } - handle(w, r, cfg, http.StatusOK) + handlers.Handle(w, r, cfg, http.StatusOK) } func (h {{.KindLowerSingular}}Handler) List(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ + cfg := &handlers.HandlerConfig{ Action: func() (interface{}, *errors.ServiceError) { ctx := r.Context() listArgs := services.NewListArguments(r.URL.Query()) - var {{.KindLowerPlural}} []api.{{.Kind}} + var {{.KindLowerPlural}} []{{.Kind}} paging, err := h.generic.List(ctx, "username", listArgs, &{{.KindLowerPlural}}) if err != nil { return nil, err @@ -99,7 +107,7 @@ func (h {{.KindLowerSingular}}Handler) List(w http.ResponseWriter, r *http.Reque } for _, {{.KindLowerSingular}} := range {{.KindLowerPlural}} { - converted := presenters.Present{{.Kind}}(&{{.KindLowerSingular}}) + converted := Present{{.Kind}}(&{{.KindLowerSingular}}) {{.KindLowerSingular}}List.Items = append({{.KindLowerSingular}}List.Items, converted) } if listArgs.Fields != nil { @@ -113,11 +121,11 @@ func (h {{.KindLowerSingular}}Handler) List(w http.ResponseWriter, r *http.Reque }, } - handleList(w, r, cfg) + handlers.HandleList(w, r, cfg) } func (h {{.KindLowerSingular}}Handler) Get(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ + cfg := &handlers.HandlerConfig{ Action: func() (interface{}, *errors.ServiceError) { id := mux.Vars(r)["id"] ctx := r.Context() @@ -126,18 +134,24 @@ func (h {{.KindLowerSingular}}Handler) Get(w http.ResponseWriter, r *http.Reques return nil, err } - return presenters.Present{{.Kind}}({{.KindLowerSingular}}), nil + return Present{{.Kind}}({{.KindLowerSingular}}), nil }, } - handleGet(w, r, cfg) + handlers.HandleGet(w, r, cfg) } func (h {{.KindLowerSingular}}Handler) Delete(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ + cfg := &handlers.HandlerConfig{ Action: func() (interface{}, *errors.ServiceError) { - return nil, errors.NotImplemented("delete") + id := mux.Vars(r)["id"] + ctx := r.Context() + err := h.{{.KindLowerSingular}}.Delete(ctx, id) + if err != nil { + return nil, err + } + return nil, nil }, } - handleDelete(w, r, cfg, http.StatusNoContent) + handlers.HandleDelete(w, r, cfg, http.StatusNoContent) } diff --git a/templates/generate-migration.txt b/templates/generate-migration.txt index 324cafe..b1fd920 100755 --- a/templates/generate-migration.txt +++ b/templates/generate-migration.txt @@ -1,14 +1,14 @@ -package migrations +package {{.KindLowerPlural}} import ( - "gorm.io/gorm" - "github.com/go-gormigrate/gormigrate/v2" + "github.com/openshift-online/rh-trex/pkg/db" + "gorm.io/gorm" ) -func add{{.Kind}}s() *gormigrate.Migration { +func migration() *gormigrate.Migration { type {{.Kind}} struct { - Model + db.Model {{- range .Fields}} {{.Name}} {{.GoType}} {{- end}} diff --git a/templates/generate-mock.txt b/templates/generate-mock.txt index 7a420ca..350d26b 100755 --- a/templates/generate-mock.txt +++ b/templates/generate-mock.txt @@ -1,26 +1,24 @@ -package mocks +package {{.KindLowerPlural}} import ( "context" "gorm.io/gorm" - "{{.Repo}}/{{.Project}}/pkg/api" - "{{.Repo}}/{{.Project}}/pkg/dao" - "{{.Repo}}/{{.Project}}/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/errors" ) -var _ dao.{{.Kind}}Dao = &{{.KindLowerSingular}}DaoMock{} +var _ {{.Kind}}Dao = &{{.KindLowerSingular}}DaoMock{} type {{.KindLowerSingular}}DaoMock struct { - {{.KindLowerPlural}} api.{{.Kind}}List + {{.KindLowerPlural}} {{.Kind}}List } -func New{{.Kind}}Dao() *{{.KindLowerSingular}}DaoMock { +func NewMock{{.Kind}}Dao() *{{.KindLowerSingular}}DaoMock { return &{{.KindLowerSingular}}DaoMock{} } -func (d *{{.KindLowerSingular}}DaoMock) Get(ctx context.Context, id string) (*api.{{.Kind}}, error) { +func (d *{{.KindLowerSingular}}DaoMock) Get(ctx context.Context, id string) (*{{.Kind}}, error) { for _, {{.KindLowerSingular}} := range d.{{.KindLowerPlural}} { if {{.KindLowerSingular}}.ID == id { return {{.KindLowerSingular}}, nil @@ -29,12 +27,12 @@ func (d *{{.KindLowerSingular}}DaoMock) Get(ctx context.Context, id string) (*ap return nil, gorm.ErrRecordNotFound } -func (d *{{.KindLowerSingular}}DaoMock) Create(ctx context.Context, {{.KindLowerSingular}} *api.{{.Kind}}) (*api.{{.Kind}}, error) { +func (d *{{.KindLowerSingular}}DaoMock) Create(ctx context.Context, {{.KindLowerSingular}} *{{.Kind}}) (*{{.Kind}}, error) { d.{{.KindLowerPlural}} = append(d.{{.KindLowerPlural}}, {{.KindLowerSingular}}) return {{.KindLowerSingular}}, nil } -func (d *{{.KindLowerSingular}}DaoMock) Replace(ctx context.Context, {{.KindLowerSingular}} *api.{{.Kind}}) (*api.{{.Kind}}, error) { +func (d *{{.KindLowerSingular}}DaoMock) Replace(ctx context.Context, {{.KindLowerSingular}} *{{.Kind}}) (*{{.Kind}}, error) { return nil, errors.NotImplemented("{{.Kind}}").AsError() } @@ -42,10 +40,10 @@ func (d *{{.KindLowerSingular}}DaoMock) Delete(ctx context.Context, id string) e return errors.NotImplemented("{{.Kind}}").AsError() } -func (d *{{.KindLowerSingular}}DaoMock) FindByIDs(ctx context.Context, ids []string) (api.{{.Kind}}List, error) { +func (d *{{.KindLowerSingular}}DaoMock) FindByIDs(ctx context.Context, ids []string) ({{.Kind}}List, error) { return nil, errors.NotImplemented("{{.Kind}}").AsError() } -func (d *{{.KindLowerSingular}}DaoMock) All(ctx context.Context) (api.{{.Kind}}List, error) { +func (d *{{.KindLowerSingular}}DaoMock) All(ctx context.Context) ({{.Kind}}List, error) { return d.{{.KindLowerPlural}}, nil } diff --git a/templates/generate-plugin.txt b/templates/generate-plugin.txt index da548b6..cd1a293 100755 --- a/templates/generate-plugin.txt +++ b/templates/generate-plugin.txt @@ -5,35 +5,30 @@ import ( "github.com/gorilla/mux" "{{.Repo}}/{{.Project}}/cmd/{{.Cmd}}/environments" - "{{.Repo}}/{{.Project}}/cmd/{{.Cmd}}/environments/registry" - "{{.Repo}}/{{.Project}}/cmd/{{.Cmd}}/server" - "{{.Repo}}/{{.Project}}/pkg/api" - "{{.Repo}}/{{.Project}}/pkg/api/presenters" - "{{.Repo}}/{{.Project}}/pkg/auth" - "{{.Repo}}/{{.Project}}/pkg/controllers" - "{{.Repo}}/{{.Project}}/pkg/dao" - "{{.Repo}}/{{.Project}}/pkg/db" - "{{.Repo}}/{{.Project}}/pkg/handlers" - "{{.Repo}}/{{.Project}}/pkg/services" - "{{.Repo}}/{{.Project}}/plugins/events" - "{{.Repo}}/{{.Project}}/plugins/generic" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/api/presenters" + "github.com/openshift-online/rh-trex/pkg/auth" + "github.com/openshift-online/rh-trex/pkg/controllers" + "github.com/openshift-online/rh-trex/pkg/db" + pkgregistry "github.com/openshift-online/rh-trex/pkg/registry" + pkgserver "github.com/openshift-online/rh-trex/pkg/server" + "github.com/openshift-online/rh-trex/plugins/events" + "github.com/openshift-online/rh-trex/plugins/generic" ) -// ServiceLocator Service Locator -type ServiceLocator func() services.{{.Kind}}Service +type ServiceLocator func() {{.Kind}}Service func NewServiceLocator(env *environments.Env) ServiceLocator { - return func() services.{{.Kind}}Service { - return services.New{{.Kind}}Service( + return func() {{.Kind}}Service { + return New{{.Kind}}Service( db.NewAdvisoryLockFactory(env.Database.SessionFactory), - dao.New{{.Kind}}Dao(&env.Database.SessionFactory), + New{{.Kind}}Dao(&env.Database.SessionFactory), events.Service(&env.Services), ) } } -// Service helper function to get the {{.KindLowerSingular}} service from the registry -func Service(s *environments.Services) services.{{.Kind}}Service { +func Service(s *environments.Services) {{.Kind}}Service { if s == nil { return nil } @@ -45,15 +40,13 @@ func Service(s *environments.Services) services.{{.Kind}}Service { } func init() { - // Service registration - registry.RegisterService("{{.KindPlural}}", func(env interface{}) interface{} { + pkgregistry.RegisterService("{{.KindPlural}}", func(env interface{}) interface{} { return NewServiceLocator(env.(*environments.Env)) }) - // Routes registration - server.RegisterRoutes("{{.KindLowerPlural}}", func(apiV1Router *mux.Router, services server.ServicesInterface, authMiddleware auth.JWTMiddleware, authzMiddleware auth.AuthorizationMiddleware) { + pkgserver.RegisterRoutes("{{.KindLowerPlural}}", func(apiV1Router *mux.Router, services pkgserver.ServicesInterface, authMiddleware auth.JWTMiddleware, authzMiddleware auth.AuthorizationMiddleware) { envServices := services.(*environments.Services) - {{.KindLowerSingular}}Handler := handlers.New{{.Kind}}Handler(Service(envServices), generic.Service(envServices)) + {{.KindLowerSingular}}Handler := New{{.Kind}}Handler(Service(envServices), generic.Service(envServices)) {{.KindLowerPlural}}Router := apiV1Router.PathPrefix("/{{.KindSnakeCasePlural}}").Subrouter() {{.KindLowerPlural}}Router.HandleFunc("", {{.KindLowerSingular}}Handler.List).Methods(http.MethodGet) @@ -65,9 +58,9 @@ func init() { {{.KindLowerPlural}}Router.Use(authzMiddleware.AuthorizeApi) }) - // Controller registration - server.RegisterController("{{.KindPlural}}", func(manager *controllers.KindControllerManager, services *environments.Services) { - {{.KindLowerSingular}}Services := Service(services) + pkgserver.RegisterController("{{.KindPlural}}", func(manager *controllers.KindControllerManager, services pkgserver.ServicesInterface) { + envServices := services.(*environments.Services) + {{.KindLowerSingular}}Services := Service(envServices) manager.Add(&controllers.ControllerConfig{ Source: "{{.KindPlural}}", @@ -79,9 +72,10 @@ func init() { }) }) - // Presenter registration - presenters.RegisterPath(api.{{.Kind}}{}, "{{.KindSnakeCasePlural}}") - presenters.RegisterPath(&api.{{.Kind}}{}, "{{.KindSnakeCasePlural}}") - presenters.RegisterKind(api.{{.Kind}}{}, "{{.Kind}}") - presenters.RegisterKind(&api.{{.Kind}}{}, "{{.Kind}}") + presenters.RegisterPath({{.Kind}}{}, "{{.KindSnakeCasePlural}}") + presenters.RegisterPath(&{{.Kind}}{}, "{{.KindSnakeCasePlural}}") + presenters.RegisterKind({{.Kind}}{}, "{{.Kind}}") + presenters.RegisterKind(&{{.Kind}}{}, "{{.Kind}}") + + db.RegisterMigration(migration()) } diff --git a/templates/generate-presenters.txt b/templates/generate-presenters.txt index 7db7eef..b472063 100755 --- a/templates/generate-presenters.txt +++ b/templates/generate-presenters.txt @@ -1,4 +1,4 @@ -package presenters +package {{.KindLowerPlural}} import ( {{- $hasTime := false}} @@ -11,13 +11,14 @@ import ( "time" {{- end}} - "{{.Repo}}/{{.Project}}/pkg/api" + "github.com/openshift-online/rh-trex/pkg/api" "{{.Repo}}/{{.Project}}/pkg/api/openapi" - "{{.Repo}}/{{.Project}}/pkg/util" + "github.com/openshift-online/rh-trex/pkg/api/presenters" + "github.com/openshift-online/rh-trex/pkg/util" ) -func Convert{{.Kind}}({{.KindLowerSingular}} openapi.{{.Kind}}) *api.{{.Kind}} { - c := &api.{{.Kind}}{ +func Convert{{.Kind}}({{.KindLowerSingular}} openapi.{{.Kind}}) *{{.Kind}} { + c := &{{.Kind}}{ Meta: api.Meta{ ID: util.NilToEmptyString({{.KindLowerSingular}}.Id), }, @@ -56,8 +57,8 @@ func Convert{{.Kind}}({{.KindLowerSingular}} openapi.{{.Kind}}) *api.{{.Kind}} { return c } -func Present{{.Kind}}({{.KindLowerSingular}} *api.{{.Kind}}) openapi.{{.Kind}} { - reference := PresentReference({{.KindLowerSingular}}.ID, {{.KindLowerSingular}}) +func Present{{.Kind}}({{.KindLowerSingular}} *{{.Kind}}) openapi.{{.Kind}} { + reference := presenters.PresentReference({{.KindLowerSingular}}.ID, {{.KindLowerSingular}}) return openapi.{{.Kind}}{ Id: reference.Id, Kind: reference.Kind, diff --git a/templates/generate-servicelocator.txt b/templates/generate-servicelocator.txt deleted file mode 100755 index e5918c8..0000000 --- a/templates/generate-servicelocator.txt +++ /dev/null @@ -1,42 +0,0 @@ -package {{.KindLowerPlural}} - -import ( - "{{.Repo}}/{{.Project}}/cmd/registry-credentials-service/environments" - "{{.Repo}}/{{.Project}}/cmd/registry-credentials-service/environments/registry" - "{{.Repo}}/{{.Project}}/pkg/dao" - "{{.Repo}}/{{.Project}}/pkg/db" - "{{.Repo}}/{{.Project}}/pkg/services" - "{{.Repo}}/{{.Project}}/plugins/events" -) - -// Service Locator -type {{.Kind}}ServiceLocator func() services.{{.Kind}}Service - -func New{{.Kind}}ServiceLocator(env *environments.Env) {{.Kind}}ServiceLocator { - return func() services.{{.Kind}}Service { - return services.New{{.Kind}}Service( - db.NewAdvisoryLockFactory(env.Database.SessionFactory), - dao.New{{.Kind}}Dao(&env.Database.SessionFactory), - events.EventService(&env.Services), - ) - } -} - -// {{.Kind}}Service helper function to get the {{.KindLowerSingular}} service from the registry -func {{.Kind}}Service(s *environments.Services) services.{{.Kind}}Service { - if s == nil { - return nil - } - if obj := s.GetService("{{.KindPlural}}"); obj != nil { - locator := obj.({{.Kind}}ServiceLocator) - return locator() - } - return nil -} - -func init() { - // Service registration - registry.RegisterService("{{.KindPlural}}", func(env interface{}) interface{} { - return New{{.Kind}}ServiceLocator(env.(*environments.Env)) - }) -} diff --git a/templates/generate-services.txt b/templates/generate-services.txt index e070e7f..2abb2a6 100755 --- a/templates/generate-services.txt +++ b/templates/generate-services.txt @@ -1,30 +1,36 @@ -package services +package {{.KindLowerPlural}} import ( "context" - "{{.Repo}}/{{.Project}}/pkg/dao" - "{{.Repo}}/{{.Project}}/pkg/db" - "{{.Repo}}/{{.Project}}/pkg/logger" - "{{.Repo}}/{{.Project}}/pkg/api" - "{{.Repo}}/{{.Project}}/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/db" + "github.com/openshift-online/rh-trex/pkg/errors" + "github.com/openshift-online/rh-trex/pkg/logger" + "github.com/openshift-online/rh-trex/pkg/services" +) + +const {{.KindLowerPlural}}LockType db.LockType = "{{.KindSnakeCasePlural}}" + +var ( + DisableAdvisoryLock = false + UseBlockingAdvisoryLock = true ) type {{.Kind}}Service interface { - Get(ctx context.Context, id string) (*api.{{.Kind}}, *errors.ServiceError) - Create(ctx context.Context, {{.KindLowerSingular}} *api.{{.Kind}}) (*api.{{.Kind}}, *errors.ServiceError) - Replace(ctx context.Context, {{.KindLowerSingular}} *api.{{.Kind}}) (*api.{{.Kind}}, *errors.ServiceError) + Get(ctx context.Context, id string) (*{{.Kind}}, *errors.ServiceError) + Create(ctx context.Context, {{.KindLowerSingular}} *{{.Kind}}) (*{{.Kind}}, *errors.ServiceError) + Replace(ctx context.Context, {{.KindLowerSingular}} *{{.Kind}}) (*{{.Kind}}, *errors.ServiceError) Delete(ctx context.Context, id string) *errors.ServiceError - All(ctx context.Context) (api.{{.Kind}}List, *errors.ServiceError) + All(ctx context.Context) ({{.Kind}}List, *errors.ServiceError) - FindByIDs(ctx context.Context, ids []string) (api.{{.Kind}}List, *errors.ServiceError) + FindByIDs(ctx context.Context, ids []string) ({{.Kind}}List, *errors.ServiceError) - // idempotent functions for the control plane, but can also be called synchronously by any actor OnUpsert(ctx context.Context, id string) error OnDelete(ctx context.Context, id string) error } -func New{{.Kind}}Service(lockFactory db.LockFactory, {{.KindLowerSingular}}Dao dao.{{.Kind}}Dao, events EventService) {{.Kind}}Service { +func New{{.Kind}}Service(lockFactory db.LockFactory, {{.KindLowerSingular}}Dao {{.Kind}}Dao, events services.EventService) {{.Kind}}Service { return &sql{{.Kind}}Service{ lockFactory: lockFactory, {{.KindLowerSingular}}Dao: {{.KindLowerSingular}}Dao, @@ -36,22 +42,41 @@ var _ {{.Kind}}Service = &sql{{.Kind}}Service{} type sql{{.Kind}}Service struct { lockFactory db.LockFactory - {{.KindLowerSingular}}Dao dao.{{.Kind}}Dao - events EventService + {{.KindLowerSingular}}Dao {{.Kind}}Dao + events services.EventService +} + +func (s *sql{{.Kind}}Service) OnUpsert(ctx context.Context, id string) error { + logger := logger.NewOCMLogger(ctx) + + {{.KindLowerSingular}}, err := s.{{.KindLowerSingular}}Dao.Get(ctx, id) + if err != nil { + return err + } + + logger.Infof("Do idempotent somethings with this {{.KindLowerSingular}}: %s", {{.KindLowerSingular}}.ID) + + return nil +} + +func (s *sql{{.Kind}}Service) OnDelete(ctx context.Context, id string) error { + logger := logger.NewOCMLogger(ctx) + logger.Infof("This {{.KindLowerSingular}} has been deleted: %s", id) + return nil } -func (s *sql{{.Kind}}Service) Get(ctx context.Context, id string) (*api.{{.Kind}}, *errors.ServiceError) { +func (s *sql{{.Kind}}Service) Get(ctx context.Context, id string) (*{{.Kind}}, *errors.ServiceError) { {{.KindLowerSingular}}, err := s.{{.KindLowerSingular}}Dao.Get(ctx, id) if err != nil { - return nil, handleGetError("{{.Kind}}", "id", id, err) + return nil, services.HandleGetError("{{.Kind}}", "id", id, err) } return {{.KindLowerSingular}}, nil } -func (s *sql{{.Kind}}Service) Create(ctx context.Context, {{.KindLowerSingular}} *api.{{.Kind}}) (*api.{{.Kind}}, *errors.ServiceError) { +func (s *sql{{.Kind}}Service) Create(ctx context.Context, {{.KindLowerSingular}} *{{.Kind}}) (*{{.Kind}}, *errors.ServiceError) { {{.KindLowerSingular}}, err := s.{{.KindLowerSingular}}Dao.Create(ctx, {{.KindLowerSingular}}) if err != nil { - return nil, handleCreateError("{{.Kind}}", err) + return nil, services.HandleCreateError("{{.Kind}}", err) } _, evErr := s.events.Create(ctx, &api.Event{ @@ -60,16 +85,35 @@ func (s *sql{{.Kind}}Service) Create(ctx context.Context, {{.KindLowerSingular}} EventType: api.CreateEventType, }) if evErr != nil { - return nil, handleCreateError("{{.Kind}}", evErr) + return nil, services.HandleCreateError("{{.Kind}}", evErr) } return {{.KindLowerSingular}}, nil } -func (s *sql{{.Kind}}Service) Replace(ctx context.Context, {{.KindLowerSingular}} *api.{{.Kind}}) (*api.{{.Kind}}, *errors.ServiceError) { +func (s *sql{{.Kind}}Service) Replace(ctx context.Context, {{.KindLowerSingular}} *{{.Kind}}) (*{{.Kind}}, *errors.ServiceError) { + if !DisableAdvisoryLock { + if UseBlockingAdvisoryLock { + lockOwnerID, err := s.lockFactory.NewAdvisoryLock(ctx, {{.KindLowerSingular}}.ID, {{.KindLowerPlural}}LockType) + if err != nil { + return nil, errors.DatabaseAdvisoryLock(err) + } + defer s.lockFactory.Unlock(ctx, lockOwnerID) + } else { + lockOwnerID, locked, err := s.lockFactory.NewNonBlockingLock(ctx, {{.KindLowerSingular}}.ID, {{.KindLowerPlural}}LockType) + if err != nil { + return nil, errors.DatabaseAdvisoryLock(err) + } + if !locked { + return nil, services.HandleCreateError("{{.Kind}}", errors.New(errors.ErrorConflict, "row locked")) + } + defer s.lockFactory.Unlock(ctx, lockOwnerID) + } + } + {{.KindLowerSingular}}, err := s.{{.KindLowerSingular}}Dao.Replace(ctx, {{.KindLowerSingular}}) if err != nil { - return nil, handleUpdateError("{{.Kind}}", err) + return nil, services.HandleUpdateError("{{.Kind}}", err) } _, evErr := s.events.Create(ctx, &api.Event{ @@ -78,7 +122,7 @@ func (s *sql{{.Kind}}Service) Replace(ctx context.Context, {{.KindLowerSingular} EventType: api.UpdateEventType, }) if evErr != nil { - return nil, handleUpdateError("{{.Kind}}", evErr) + return nil, services.HandleUpdateError("{{.Kind}}", evErr) } return {{.KindLowerSingular}}, nil @@ -86,7 +130,7 @@ func (s *sql{{.Kind}}Service) Replace(ctx context.Context, {{.KindLowerSingular} func (s *sql{{.Kind}}Service) Delete(ctx context.Context, id string) *errors.ServiceError { if err := s.{{.KindLowerSingular}}Dao.Delete(ctx, id); err != nil { - return handleDeleteError("{{.Kind}}", errors.GeneralError("Unable to delete {{.KindLowerSingular}}: %s", err)) + return services.HandleDeleteError("{{.Kind}}", errors.GeneralError("Unable to delete {{.KindLowerSingular}}: %s", err)) } _, evErr := s.events.Create(ctx, &api.Event{ @@ -95,13 +139,13 @@ func (s *sql{{.Kind}}Service) Delete(ctx context.Context, id string) *errors.Ser EventType: api.DeleteEventType, }) if evErr != nil { - return handleDeleteError("{{.Kind}}", evErr) + return services.HandleDeleteError("{{.Kind}}", evErr) } return nil } -func (s *sql{{.Kind}}Service) FindByIDs(ctx context.Context, ids []string) (api.{{.Kind}}List, *errors.ServiceError) { +func (s *sql{{.Kind}}Service) FindByIDs(ctx context.Context, ids []string) ({{.Kind}}List, *errors.ServiceError) { {{.KindLowerPlural}}, err := s.{{.KindLowerSingular}}Dao.FindByIDs(ctx, ids) if err != nil { return nil, errors.GeneralError("Unable to get all {{.KindLowerPlural}}: %s", err) @@ -109,29 +153,10 @@ func (s *sql{{.Kind}}Service) FindByIDs(ctx context.Context, ids []string) (api. return {{.KindLowerPlural}}, nil } -func (s *sql{{.Kind}}Service) All(ctx context.Context) (api.{{.Kind}}List, *errors.ServiceError) { +func (s *sql{{.Kind}}Service) All(ctx context.Context) ({{.Kind}}List, *errors.ServiceError) { {{.KindLowerPlural}}, err := s.{{.KindLowerSingular}}Dao.All(ctx) if err != nil { return nil, errors.GeneralError("Unable to get all {{.KindLowerPlural}}: %s", err) } return {{.KindLowerPlural}}, nil } - -func (s *sql{{.Kind}}Service) OnUpsert(ctx context.Context, id string) error { - logger := logger.NewOCMLogger(ctx) - - {{.KindLowerSingular}}, err := s.{{.KindLowerSingular}}Dao.Get(ctx, id) - if err != nil { - return err - } - - logger.Infof("Do idempotent somethings with this {{.KindLowerSingular}}: %s", {{.KindLowerSingular}}.ID) - - return nil -} - -func (s *sql{{.Kind}}Service) OnDelete(ctx context.Context, id string) error { - logger := logger.NewOCMLogger(ctx) - logger.Infof("This {{.KindLowerSingular}} has been deleted: %s", id) - return nil -} diff --git a/templates/generate-test-factories.txt b/templates/generate-test-factories.txt index 1a571c0..2324948 100755 --- a/templates/generate-test-factories.txt +++ b/templates/generate-test-factories.txt @@ -1,7 +1,8 @@ -package factories +package {{.KindLowerPlural}}_test import ( "context" + "fmt" {{- $hasTime := false}} {{- range .Fields}} {{- if eq .Type "time"}} @@ -11,11 +12,69 @@ import ( {{- if $hasTime}} "time" {{- end}} - "{{.Repo}}/{{.Project}}/cmd/registry-credentials-service/environments" - "{{.Repo}}/{{.Project}}/pkg/api" + + "{{.Repo}}/{{.Project}}/cmd/{{.Cmd}}/environments" "{{.Repo}}/{{.Project}}/plugins/{{.KindLowerPlural}}" ) +func new{{.Kind}}(id string) (*{{.KindLowerPlural}}.{{.Kind}}, error) { + {{.KindLowerSingular}}Service := {{.KindLowerPlural}}.Service(&environments.Environment().Services) + + {{.KindLowerSingular}} := &{{.KindLowerPlural}}.{{.Kind}}{ +{{- range .Fields}} +{{- if .Nullable}} +{{- if eq .Type "string"}} + {{.Name}}: stringPtr("test-{{.NameSnakeCase}}"), +{{- else if eq .Type "int"}} + {{.Name}}: intPtr(42), +{{- else if eq .Type "int64"}} + {{.Name}}: int64Ptr(42), +{{- else if eq .Type "bool"}} + {{.Name}}: boolPtr(true), +{{- else if eq .Type "float"}} + {{.Name}}: float64Ptr(3.14), +{{- else if eq .Type "time"}} + {{.Name}}: timePtr(time.Now()), +{{- end}} +{{- else}} +{{- if eq .Type "string"}} + {{.Name}}: "test-{{.NameSnakeCase}}", +{{- else if eq .Type "int"}} + {{.Name}}: 42, +{{- else if eq .Type "int64"}} + {{.Name}}: 42, +{{- else if eq .Type "bool"}} + {{.Name}}: true, +{{- else if eq .Type "float"}} + {{.Name}}: 3.14, +{{- else if eq .Type "time"}} + {{.Name}}: time.Now(), +{{- end}} +{{- end}} +{{- end}} + } + + sub, err := {{.KindLowerSingular}}Service.Create(context.Background(), {{.KindLowerSingular}}) + if err != nil { + return nil, err + } + + return sub, nil +} + +func new{{.Kind}}List(namePrefix string, count int) ([]*{{.KindLowerPlural}}.{{.Kind}}, error) { + var items []*{{.KindLowerPlural}}.{{.Kind}} + for i := 1; i <= count; i++ { + name := fmt.Sprintf("%s_%d", namePrefix, i) + c, err := new{{.Kind}}(name) + if err != nil { + return nil, err + } + items = append(items, c) + } + return items, nil +} + {{- $hasNullableString := false}} {{- $hasNullableInt := false}} {{- $hasNullableInt64 := false}} @@ -34,7 +93,6 @@ import ( {{- end}} {{- if or $hasNullableString $hasNullableInt $hasNullableInt64 $hasNullableBool $hasNullableFloat $hasNullableTime}} -// Helper functions for pointer types {{- if $hasNullableString}} func stringPtr(s string) *string { return &s } {{- end}} @@ -54,67 +112,3 @@ func float64Ptr(f float64) *float64 { return &f } func timePtr(t time.Time) *time.Time { return &t } {{- end}} {{- end}} - -func (f *Factories) New{{.Kind}}(id string) (*api.{{.Kind}}, error) { - {{.KindLowerSingular}}Service := {{.KindLowerPlural}}.Service(&environments.Environment().Services) - - {{.KindLowerSingular}} := &api.{{.Kind}}{ - Meta: api.Meta{ID: id}, -{{- range .Fields}} -{{- if .Nullable}} -{{- if eq .Type "string"}} - {{.Name}}: stringPtr("test-{{.NameSnakeCase}}"), -{{- else if eq .Type "int"}} - {{.Name}}: intPtr(42), -{{- else if eq .Type "int64"}} - {{.Name}}: int64Ptr(42), -{{- else if eq .Type "bool"}} - {{.Name}}: boolPtr(true), -{{- else if eq .Type "float"}} - {{.Name}}: float64Ptr(3.14), -{{- else if eq .Type "time"}} - {{.Name}}: timePtr(time.Now()), -{{- end}} -{{- else}} -{{- if eq .Type "string"}} - {{.Name}}: "test-{{.NameSnakeCase}}", -{{- else if eq .Type "int"}} - {{.Name}}: 42, -{{- else if eq .Type "int64"}} - {{.Name}}: 42, -{{- else if eq .Type "bool"}} - {{.Name}}: true, -{{- else if eq .Type "float"}} - {{.Name}}: 3.14, -{{- else if eq .Type "time"}} - {{.Name}}: time.Now(), -{{- end}} -{{- end}} -{{- end}} - } - - sub, err := {{.KindLowerSingular}}Service.Create(context.Background(), {{.KindLowerSingular}}) - if err != nil { - return nil, err - } - - return sub, nil -} - -func (f *Factories) New{{.Kind}}List(name string, count int) ([]*api.{{.Kind}}, error) { - var {{.KindPlural}} []*api.{{.Kind}} - for i := 1; i <= count; i++ { - c, _ := f.New{{.Kind}}(f.NewID()) - {{.KindPlural}} = append({{.KindPlural}}, c) - } - return {{.KindPlural}}, nil -} - -// Aliases for test compatibility -func (f *Factories) New{{.KindPlural}}(id string) (*api.{{.Kind}}, error) { - return f.New{{.Kind}}(id) -} - -func (f *Factories) New{{.KindPlural}}List(name string, count int) ([]*api.{{.Kind}}, error) { - return f.New{{.Kind}}List(name, count) -} diff --git a/templates/generate-test.txt b/templates/generate-test.txt index 7335146..428d7d1 100755 --- a/templates/generate-test.txt +++ b/templates/generate-test.txt @@ -1,4 +1,4 @@ -package integration +package {{.KindLowerPlural}}_test import ( "context" @@ -28,19 +28,17 @@ func Test{{.Kind}}Get(t *testing.T) { account := h.NewRandAccount() ctx := h.NewAuthenticatedContext(account) - // 401 using no JWT token - _, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1{{.KindPlural}}IdGet(context.Background(), "foo").Execute() + _, _, err := client.DefaultAPI.Api{{.ProjectPascalCase}}V1{{.KindPlural}}IdGet(context.Background(), "foo").Execute() Expect(err).To(HaveOccurred(), "Expected 401 but got nil error") - // GET responses per openapi spec: 200 and 404, - _, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1{{.KindPlural}}IdGet(ctx, "foo").Execute() + _, resp, err := client.DefaultAPI.Api{{.ProjectPascalCase}}V1{{.KindPlural}}IdGet(ctx, "foo").Execute() Expect(err).To(HaveOccurred(), "Expected 404") Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) - {{.KindLowerSingular}}Model, err := h.Factories.New{{.KindPlural}}(h.NewID()) - Expect(err).NotTo(HaveOccurred()) + {{.KindLowerSingular}}Model, err := new{{.Kind}}(h.NewID()) + Expect(err).NotTo(HaveOccurred()) - {{.KindLowerSingular}}Output, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1{{.KindPlural}}IdGet(ctx, {{.KindLowerSingular}}Model.ID).Execute() + {{.KindLowerSingular}}Output, resp, err := client.DefaultAPI.Api{{.ProjectPascalCase}}V1{{.KindPlural}}IdGet(ctx, {{.KindLowerSingular}}Model.ID).Execute() Expect(err).NotTo(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusOK)) @@ -57,7 +55,6 @@ func Test{{.Kind}}Post(t *testing.T) { account := h.NewRandAccount() ctx := h.NewAuthenticatedContext(account) - // POST responses per openapi spec: 201, 409, 500 {{.KindLowerSingular}}Input := openapi.{{.Kind}}{ {{- range .Fields}} {{- if .Nullable}} @@ -92,15 +89,13 @@ func Test{{.Kind}}Post(t *testing.T) { {{- end}} } - // 201 Created - {{.KindLowerSingular}}Output, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1{{.KindPlural}}Post(ctx).{{.Kind}}({{.KindLowerSingular}}Input).Execute() + {{.KindLowerSingular}}Output, resp, err := client.DefaultAPI.Api{{.ProjectPascalCase}}V1{{.KindPlural}}Post(ctx).{{.Kind}}({{.KindLowerSingular}}Input).Execute() Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) Expect(resp.StatusCode).To(Equal(http.StatusCreated)) Expect(*{{.KindLowerSingular}}Output.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") Expect(*{{.KindLowerSingular}}Output.Kind).To(Equal("{{.Kind}}")) Expect(*{{.KindLowerSingular}}Output.Href).To(Equal(fmt.Sprintf("/api/{{.Project}}/v1/{{.KindSnakeCasePlural}}/%s", *{{.KindLowerSingular}}Output.Id))) - // 400 bad request. posting junk json is one way to trigger 400. jwtToken := ctx.Value(openapi.ContextAccessToken) restyResp, err := resty.R(). SetHeader("Content-Type", "application/json"). @@ -117,13 +112,10 @@ func Test{{.Kind}}Patch(t *testing.T) { account := h.NewRandAccount() ctx := h.NewAuthenticatedContext(account) - // POST responses per openapi spec: 201, 409, 500 - - {{.KindLowerSingular}}Model, err := h.Factories.New{{.KindPlural}}(h.NewID()) - Expect(err).NotTo(HaveOccurred()) + {{.KindLowerSingular}}Model, err := new{{.Kind}}(h.NewID()) + Expect(err).NotTo(HaveOccurred()) - // 200 OK - {{.KindLowerSingular}}Output, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1{{.KindPlural}}IdPatch(ctx, {{.KindLowerSingular}}Model.ID).{{.Kind}}PatchRequest(openapi.{{.Kind}}PatchRequest{}).Execute() + {{.KindLowerSingular}}Output, resp, err := client.DefaultAPI.Api{{.ProjectPascalCase}}V1{{.KindPlural}}IdPatch(ctx, {{.KindLowerSingular}}Model.ID).{{.Kind}}PatchRequest(openapi.{{.Kind}}PatchRequest{}).Execute() Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) Expect(resp.StatusCode).To(Equal(http.StatusOK)) Expect(*{{.KindLowerSingular}}Output.Id).To(Equal({{.KindLowerSingular}}Model.ID)) @@ -132,7 +124,6 @@ func Test{{.Kind}}Patch(t *testing.T) { Expect(*{{.KindLowerSingular}}Output.Href).To(Equal(fmt.Sprintf("/api/{{.Project}}/v1/{{.KindSnakeCasePlural}}/%s", *{{.KindLowerSingular}}Output.Id))) jwtToken := ctx.Value(openapi.ContextAccessToken) - // 500 server error. posting junk json is one way to trigger 500. restyResp, err := resty.R(). SetHeader("Content-Type", "application/json"). SetHeader("Authorization", fmt.Sprintf("Bearer %s", jwtToken)). @@ -148,18 +139,17 @@ func Test{{.Kind}}Paging(t *testing.T) { account := h.NewRandAccount() ctx := h.NewAuthenticatedContext(account) - // Paging - _, err := h.Factories.New{{.KindPlural}}List("Bronto", 20) - Expect(err).NotTo(HaveOccurred()) + _, err := new{{.Kind}}List("Bronto", 20) + Expect(err).NotTo(HaveOccurred()) - list, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1{{.KindPlural}}Get(ctx).Execute() + list, _, err := client.DefaultAPI.Api{{.ProjectPascalCase}}V1{{.KindPlural}}Get(ctx).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting {{.KindLowerSingular}} list: %v", err) Expect(len(list.Items)).To(Equal(20)) Expect(list.Size).To(Equal(int32(20))) Expect(list.Total).To(Equal(int32(20))) Expect(list.Page).To(Equal(int32(1))) - list, _, err = client.DefaultAPI.ApiRegistryCredentialServiceV1{{.KindPlural}}Get(ctx).Page(2).Size(5).Execute() + list, _, err = client.DefaultAPI.Api{{.ProjectPascalCase}}V1{{.KindPlural}}Get(ctx).Page(2).Size(5).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting {{.KindLowerSingular}} list: %v", err) Expect(len(list.Items)).To(Equal(5)) Expect(list.Size).To(Equal(int32(5))) @@ -173,10 +163,11 @@ func Test{{.Kind}}ListSearch(t *testing.T) { account := h.NewRandAccount() ctx := h.NewAuthenticatedContext(account) - {{.KindLowerPlural}}, _ := h.Factories.New{{.KindPlural}}List("bronto", 20) + {{.KindLowerPlural}}, err := new{{.Kind}}List("bronto", 20) + Expect(err).NotTo(HaveOccurred()) search := fmt.Sprintf("id in ('%s')", {{.KindLowerPlural}}[0].ID) - list, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1{{.KindPlural}}Get(ctx).Search(search).Execute() + list, _, err := client.DefaultAPI.Api{{.ProjectPascalCase}}V1{{.KindPlural}}Get(ctx).Search(search).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting {{.KindLowerSingular}} list: %v", err) Expect(len(list.Items)).To(Equal(1)) Expect(list.Total).To(Equal(int32(1))) diff --git a/templates/generate-testmain.txt b/templates/generate-testmain.txt new file mode 100644 index 0000000..6b9d589 --- /dev/null +++ b/templates/generate-testmain.txt @@ -0,0 +1,21 @@ +package {{.KindLowerPlural}}_test + +import ( + "flag" + "os" + "runtime" + "testing" + + "github.com/golang/glog" + + "{{.Repo}}/{{.Project}}/test" +) + +func TestMain(m *testing.M) { + flag.Parse() + glog.Infof("Starting {{.KindLowerPlural}} integration test using go version %s", runtime.Version()) + helper := test.NewHelper(&testing.T{}) + exitCode := m.Run() + helper.Teardown() + os.Exit(exitCode) +} diff --git a/test/factories/accessTokens.go b/test/factories/accessTokens.go index a65fa8d..ececb95 100644 --- a/test/factories/accessTokens.go +++ b/test/factories/accessTokens.go @@ -3,14 +3,14 @@ package factories import ( "context" "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" "github.com/openshift-hyperfleet/registry-credentials-service/plugins/accessTokens" + "github.com/openshift-online/rh-trex/pkg/api" ) -func (f *Factories) NewAccessToken(id string) (*api.AccessToken, error) { +func (f *Factories) NewAccessToken(id string) (*accessTokens.AccessToken, error) { accessTokenService := accessTokens.Service(&environments.Environment().Services) - accessToken := &api.AccessToken{ + accessToken := &accessTokens.AccessToken{ Meta: api.Meta{ID: id}, } @@ -22,20 +22,19 @@ func (f *Factories) NewAccessToken(id string) (*api.AccessToken, error) { return sub, nil } -func (f *Factories) NewAccessTokenList(name string, count int) ([]*api.AccessToken, error) { - var AccessTokens []*api.AccessToken +func (f *Factories) NewAccessTokenList(name string, count int) ([]*accessTokens.AccessToken, error) { + var result []*accessTokens.AccessToken for i := 1; i <= count; i++ { c, _ := f.NewAccessToken(f.NewID()) - AccessTokens = append(AccessTokens, c) + result = append(result, c) } - return AccessTokens, nil + return result, nil } -// Aliases for test compatibility -func (f *Factories) NewAccessTokens(id string) (*api.AccessToken, error) { +func (f *Factories) NewAccessTokens(id string) (*accessTokens.AccessToken, error) { return f.NewAccessToken(id) } -func (f *Factories) NewAccessTokensList(name string, count int) ([]*api.AccessToken, error) { +func (f *Factories) NewAccessTokensList(name string, count int) ([]*accessTokens.AccessToken, error) { return f.NewAccessTokenList(name, count) } diff --git a/test/factories/dinosaurs.go b/test/factories/dinosaurs.go deleted file mode 100755 index 517091c..0000000 --- a/test/factories/dinosaurs.go +++ /dev/null @@ -1,38 +0,0 @@ -package factories - -import ( - "context" - "fmt" - - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/plugins/dinosaurs" -) - -func (f *Factories) NewDinosaur(species string) (*api.Dinosaur, error) { - dinoService := dinosaurs.Service(&environments.Environment().Services) - - dinosaur := &api.Dinosaur{ - Species: species, - } - - dino, err := dinoService.Create(context.Background(), dinosaur) - if err != nil { - return nil, err - } - - return dino, nil -} - -func (f *Factories) NewDinosaurList(namePrefix string, count int) ([]*api.Dinosaur, error) { - var dinosaurs []*api.Dinosaur - for i := 1; i <= count; i++ { - name := fmt.Sprintf("%s_%d", namePrefix, i) - c, error := f.NewDinosaur(name) - if error != nil { - return nil, error - } - dinosaurs = append(dinosaurs, c) - } - return dinosaurs, nil -} diff --git a/test/factories/registryCredentials.go b/test/factories/registryCredentials.go index a8c8711..3b272b2 100644 --- a/test/factories/registryCredentials.go +++ b/test/factories/registryCredentials.go @@ -3,15 +3,15 @@ package factories import ( "context" "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" "github.com/openshift-hyperfleet/registry-credentials-service/plugins/registryCredentials" + "github.com/openshift-online/rh-trex/pkg/api" ) -func (f *Factories) NewRegistryCredential(id string) (*api.RegistryCredential, error) { +func (f *Factories) NewRegistryCredential(id string) (*registryCredentials.RegistryCredential, error) { registryCredentialService := registryCredentials.Service(&environments.Environment().Services) accountID := "test_account_" + f.NewID() - registryCredential := &api.RegistryCredential{ + registryCredential := ®istryCredentials.RegistryCredential{ Meta: api.Meta{ID: id}, Username: "test_user_" + f.NewID(), Token: "test_token_" + f.NewID(), @@ -28,20 +28,19 @@ func (f *Factories) NewRegistryCredential(id string) (*api.RegistryCredential, e return sub, nil } -func (f *Factories) NewRegistryCredentialList(name string, count int) ([]*api.RegistryCredential, error) { - var RegistryCredentials []*api.RegistryCredential +func (f *Factories) NewRegistryCredentialList(name string, count int) ([]*registryCredentials.RegistryCredential, error) { + var result []*registryCredentials.RegistryCredential for i := 1; i <= count; i++ { c, _ := f.NewRegistryCredential(f.NewID()) - RegistryCredentials = append(RegistryCredentials, c) + result = append(result, c) } - return RegistryCredentials, nil + return result, nil } -// Aliases for test compatibility -func (f *Factories) NewRegistryCredentials(id string) (*api.RegistryCredential, error) { +func (f *Factories) NewRegistryCredentials(id string) (*registryCredentials.RegistryCredential, error) { return f.NewRegistryCredential(id) } -func (f *Factories) NewRegistryCredentialsList(name string, count int) ([]*api.RegistryCredential, error) { +func (f *Factories) NewRegistryCredentialsList(name string, count int) ([]*registryCredentials.RegistryCredential, error) { return f.NewRegistryCredentialList(name, count) } diff --git a/test/factories/registrys.go b/test/factories/registrys.go index 5adf062..5a79465 100644 --- a/test/factories/registrys.go +++ b/test/factories/registrys.go @@ -3,14 +3,14 @@ package factories import ( "context" "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" "github.com/openshift-hyperfleet/registry-credentials-service/plugins/registrys" + "github.com/openshift-online/rh-trex/pkg/api" ) -func (f *Factories) NewRegistry(id string) (*api.Registry, error) { +func (f *Factories) NewRegistry(id string) (*registrys.Registry, error) { registryService := registrys.Service(&environments.Environment().Services) - registry := &api.Registry{ + registry := ®istrys.Registry{ Meta: api.Meta{ID: id}, } @@ -22,20 +22,19 @@ func (f *Factories) NewRegistry(id string) (*api.Registry, error) { return sub, nil } -func (f *Factories) NewRegistryList(name string, count int) ([]*api.Registry, error) { - var Registrys []*api.Registry +func (f *Factories) NewRegistryList(name string, count int) ([]*registrys.Registry, error) { + var result []*registrys.Registry for i := 1; i <= count; i++ { c, _ := f.NewRegistry(f.NewID()) - Registrys = append(Registrys, c) + result = append(result, c) } - return Registrys, nil + return result, nil } -// Aliases for test compatibility -func (f *Factories) NewRegistrys(id string) (*api.Registry, error) { +func (f *Factories) NewRegistrys(id string) (*registrys.Registry, error) { return f.NewRegistry(id) } -func (f *Factories) NewRegistrysList(name string, count int) ([]*api.Registry, error) { +func (f *Factories) NewRegistrysList(name string, count int) ([]*registrys.Registry, error) { return f.NewRegistryList(name, count) } diff --git a/test/helper.go b/test/helper.go index b7455ae..5854e7e 100755 --- a/test/helper.go +++ b/test/helper.go @@ -2,41 +2,29 @@ package test import ( "context" - "crypto/rsa" - "encoding/base64" "encoding/json" "fmt" "os" - "strings" "sync" "testing" "time" - "github.com/bxcodec/faker/v3" - "github.com/golang-jwt/jwt/v4" "github.com/golang/glog" - "github.com/google/uuid" - "github.com/segmentio/ksuid" "github.com/spf13/pflag" amv1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/environments" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/server" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" + localapi "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/config" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" "github.com/openshift-hyperfleet/registry-credentials-service/test/factories" - "github.com/openshift-hyperfleet/registry-credentials-service/test/mocks" + "github.com/openshift-online/rh-trex/pkg/config" + pkgserver "github.com/openshift-online/rh-trex/pkg/server" + "github.com/openshift-online/rh-trex/pkg/testutil" ) const ( - apiPort = ":8777" - jwtKeyFile = "test/support/jwt_private_key.pem" - jwtCAFile = "test/support/jwt_ca.pem" - jwkKID = "uhctestkey" - jwkAlg = "RS256" + apiPort = ":8777" ) var ( @@ -44,38 +32,22 @@ var ( once sync.Once ) -// TODO jwk mock server needs to be refactored out of the helper and into the testing environment -var jwkURL string - -// TimeFunc defines a way to get a new Time instance common to the entire test suite. -// Aria's environment has Virtual Time that may not be actual time. We compensate -// by synchronizing on a common time func attached to the test harness. type TimeFunc func() time.Time type Helper struct { - Ctx context.Context - DBFactory db.SessionFactory - AppConfig *config.ApplicationConfig - APIServer server.Server - MetricsServer server.Server - HealthCheckServer server.Server + testutil.BaseHelper + APIServer pkgserver.Server + MetricsServer pkgserver.Server + HealthCheckServer pkgserver.Server TimeFunc TimeFunc - JWTPrivateKey *rsa.PrivateKey - JWTCA *rsa.PublicKey - T *testing.T teardowns []func() error Factories factories.Factories } func NewHelper(t *testing.T) *Helper { once.Do(func() { - jwtKey, jwtCA, err := parseJWTKeys() - if err != nil { - fmt.Println("Unable to read JWT keys - this may affect tests that make authenticated server requests") - } - env := environments.Environment() - err = env.AddFlags(pflag.CommandLine) + err := env.AddFlags(pflag.CommandLine) if err != nil { glog.Fatalf("Unable to add environment flags: %s", err.Error()) } @@ -90,15 +62,16 @@ func NewHelper(t *testing.T) *Helper { glog.Fatalf("Unable to initialize testing environment: %s", err.Error()) } + base := testutil.NewBaseHelper( + environments.Environment().Config, + environments.Environment().Database.SessionFactory, + ) + helper = &Helper{ - AppConfig: environments.Environment().Config, - DBFactory: environments.Environment().Database.SessionFactory, - JWTPrivateKey: jwtKey, - JWTCA: jwtCA, + BaseHelper: *base, } - // TODO jwk mock server needs to be refactored out of the helper and into the testing environment - jwkMockTeardown := helper.StartJWKCertServerMock() + _, jwkMockTeardown := helper.StartJWKCertServerMock() helper.teardowns = []func() error{ helper.CleanDB, jwkMockTeardown, @@ -132,9 +105,11 @@ func (helper *Helper) Teardown() { } func (helper *Helper) startAPIServer() { - // TODO jwk mock server needs to be refactored out of the helper and into the testing environment - helper.Env().Config.Server.JwkCertURL = jwkURL - helper.APIServer = server.NewAPIServer() + specData, err := localapi.GetOpenAPISpec() + if err != nil { + glog.Fatalf("Unable to load OpenAPI spec: %s", err) + } + helper.APIServer = pkgserver.NewDefaultAPIServer(environments.Environment(), specData) listener, err := helper.APIServer.Listen() if err != nil { glog.Fatalf("Unable to start Test API server: %s", err) @@ -154,7 +129,7 @@ func (helper *Helper) stopAPIServer() error { } func (helper *Helper) startMetricsServer() { - helper.MetricsServer = server.NewMetricsServer() + helper.MetricsServer = pkgserver.NewDefaultMetricsServer(environments.Environment()) go func() { glog.V(10).Info("Test Metrics server started") helper.MetricsServer.Start() @@ -169,7 +144,7 @@ func (helper *Helper) stopMetricsServer() { } func (helper *Helper) startHealthCheckServer() { - helper.HealthCheckServer = server.NewHealthCheckServer() + helper.HealthCheckServer = pkgserver.NewDefaultHealthCheckServer(environments.Environment()) go func() { glog.V(10).Info("Test health check server started") helper.HealthCheckServer.Start() @@ -192,12 +167,8 @@ func (helper *Helper) RestartMetricsServer() { func (helper *Helper) Reset() { glog.Infof("Reseting testing environment") env := environments.Environment() - // Reset the configuration env.Config = config.NewApplicationConfig() - // Re-read command-line configuration into a NEW flagset - // This new flag set ensures we don't hit conflicts defining the same flag twice - // Also on reset, we don't care to be re-defining 'v' and other glog flags flagset := pflag.NewFlagSet(helper.NewID(), pflag.ContinueOnError) env.AddFlags(flagset) pflag.Parse() @@ -210,206 +181,17 @@ func (helper *Helper) Reset() { helper.RestartServer() } -// NewID creates a new unique ID used internally to CS -func (helper *Helper) NewID() string { - return ksuid.New().String() -} - -// NewUUID creates a new unique UUID, which has different formatting than ksuid -// UUID is used by telemeter and we validate the format. -func (helper *Helper) NewUUID() string { - return uuid.New().String() -} - -func (helper *Helper) RestURL(path string) string { - protocol := "http" - if helper.AppConfig.Server.EnableHTTPS { - protocol = "https" - } - return fmt.Sprintf("%s://%s/api/registry-credential-service/v1%s", protocol, helper.AppConfig.Server.BindAddress, path) -} - -func (helper *Helper) MetricsURL(path string) string { - return fmt.Sprintf("http://%s%s", helper.AppConfig.Metrics.BindAddress, path) -} - -func (helper *Helper) HealthCheckURL(path string) string { - return fmt.Sprintf("http://%s%s", helper.AppConfig.HealthCheck.BindAddress, path) -} - func (helper *Helper) NewApiClient() *openapi.APIClient { - config := openapi.NewConfiguration() - client := openapi.NewAPIClient(config) + cfg := openapi.NewConfiguration() + client := openapi.NewAPIClient(cfg) return client } -func (helper *Helper) NewRandAccount() *amv1.Account { - return helper.NewAccount(helper.NewID(), faker.Name(), faker.Email()) -} - -func (helper *Helper) NewAccount(username, name, email string) *amv1.Account { - var firstName string - var lastName string - names := strings.SplitN(name, " ", 2) - if len(names) < 2 { - firstName = name - lastName = "" - } else { - firstName = names[0] - lastName = names[1] - } - - builder := amv1.NewAccount(). - Username(username). - FirstName(firstName). - LastName(lastName). - Email(email) - - acct, err := builder.Build() - if err != nil { - helper.T.Errorf(fmt.Sprintf("Unable to build account: %s", err)) - } - return acct -} - func (helper *Helper) NewAuthenticatedContext(account *amv1.Account) context.Context { tokenString := helper.CreateJWTString(account) return context.WithValue(context.Background(), openapi.ContextAccessToken, tokenString) } -func (helper *Helper) StartJWKCertServerMock() (teardown func() error) { - jwkURL, teardown = mocks.NewJWKCertServerMock(helper.T, helper.JWTCA, jwkKID, jwkAlg) - helper.Env().Config.Server.JwkCertURL = jwkURL - return teardown -} - -func (helper *Helper) DeleteAll(table interface{}) { - g2 := helper.DBFactory.New(context.Background()) - err := g2.Model(table).Unscoped().Delete(table).Error - if err != nil { - helper.T.Errorf("error deleting from table %v: %v", table, err) - } -} - -func (helper *Helper) Delete(obj interface{}) { - g2 := helper.DBFactory.New(context.Background()) - err := g2.Unscoped().Delete(obj).Error - if err != nil { - helper.T.Errorf("error deleting object %v: %v", obj, err) - } -} - -func (helper *Helper) SkipIfShort() { - if testing.Short() { - helper.T.Skip("Skipping execution of test in short mode") - } -} - -func (helper *Helper) Count(table string) int64 { - g2 := helper.DBFactory.New(context.Background()) - var count int64 - err := g2.Table(table).Count(&count).Error - if err != nil { - helper.T.Errorf("error getting count for table %s: %v", table, err) - } - return count -} - -func (helper *Helper) MigrateDB() error { - return db.Migrate(helper.DBFactory.New(context.Background())) -} - -func (helper *Helper) MigrateDBTo(migrationID string) { - db.MigrateTo(helper.DBFactory, migrationID) -} - -func (helper *Helper) ClearAllTables() { - helper.DeleteAll(&api.Dinosaur{}) -} - -func (helper *Helper) CleanDB() error { - g2 := helper.DBFactory.New(context.Background()) - - // TODO: this list should not be static or otherwise not hard-coded here. - for _, table := range []string{ - "dinosaurs", - "events", - "migrations", - } { - if g2.Migrator().HasTable(table) { - if err := g2.Migrator().DropTable(table); err != nil { - helper.T.Errorf("error dropping table %s: %v", table, err) - return err - } - } else { - helper.T.Errorf("Unable to drop table %q, it does not exist", table) - } - } - return nil -} - -func (helper *Helper) ResetDB() error { - if err := helper.CleanDB(); err != nil { - return err - } - - if err := helper.MigrateDB(); err != nil { - return err - } - - return nil -} - -func (helper *Helper) CreateJWTString(account *amv1.Account) string { - // Use an RH SSO JWT by default since we are phasing RHD out - claims := jwt.MapClaims{ - "iss": helper.Env().Config.OCM.TokenURL, - "username": strings.ToLower(account.Username()), - "first_name": account.FirstName(), - "last_name": account.LastName(), - "typ": "Bearer", - "iat": time.Now().Unix(), - "exp": time.Now().Add(1 * time.Hour).Unix(), - } - if account.Email() != "" { - claims["email"] = account.Email() - } - /* TODO the ocm api model needs to be updated to expose this - if account.ServiceAccount { - claims["clientId"] = account.Username() - } - */ - - token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - // Set the token header kid to the same value we expect when validating the token - // The kid is an arbitrary identifier for the key - // See https://tools.ietf.org/html/rfc7517#section-4.5 - token.Header["kid"] = jwkKID - - // private key and public key taken from http://kjur.github.io/jsjws/tool_jwt.html - // the go-jwt-middleware pkg we use does the same for their tests - signedToken, err := token.SignedString(helper.JWTPrivateKey) - if err != nil { - helper.T.Errorf("Unable to sign test jwt: %s", err) - return "" - } - return signedToken -} - -func (helper *Helper) CreateJWTToken(account *amv1.Account) *jwt.Token { - tokenString := helper.CreateJWTString(account) - - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { - return helper.JWTCA, nil - }) - if err != nil { - helper.T.Errorf("Unable to parse signed jwt: %s", err) - return nil - } - return token -} - -// OpenapiError Convert an error response from the openapi client to an openapi error struct func (helper *Helper) OpenapiError(err error) openapi.Error { generic := err.(openapi.GenericOpenAPIError) var exErr openapi.Error @@ -419,93 +201,3 @@ func (helper *Helper) OpenapiError(err error) openapi.Error { } return exErr } - -func parseJWTKeys() (*rsa.PrivateKey, *rsa.PublicKey, error) { - // projectRootDir := getProjectRootDir() - // privateBytes, err := os.ReadFile(filepath.Join(projectRootDir, jwtKeyFile)) - privateBytes, err := privatebytes() - if err != nil { - err = fmt.Errorf("unable to read JWT key file %s: %s", jwtKeyFile, err) - return nil, nil, err - } - // pubBytes, err := ioutil.ReadFile(filepath.Join(projectRootDir, jwtCAFile)) - pubBytes, err := publicbytes() - if err != nil { - err = fmt.Errorf("unable to read JWT ca file %s: %s", jwtKeyFile, err) - return nil, nil, err - } - - // Parse keys - privateKey, err := jwt.ParseRSAPrivateKeyFromPEMWithPassword(privateBytes, "passwd") - if err != nil { - err = fmt.Errorf("unable to parse JWT private key: %s", err) - return nil, nil, err - } - pubKey, err := jwt.ParseRSAPublicKeyFromPEM(pubBytes) - if err != nil { - err = fmt.Errorf("unable to parse JWT ca: %s", err) - return nil, nil, err - } - - return privateKey, pubKey, nil -} - -func privatebytes() ([]byte, error) { - s := `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpQcm9jLVR5cGU6IDQsRU5DUllQVEVECkRF -Sy1JbmZvOiBERVMtRURFMy1DQkMsMkU2NTExOEU2QzdCNTIwNwoKN2NZVVRXNFpCZG1WWjRJTEIw -OGhjVGRtNWliMEUwemN5K0k3cEhwTlFmSkh0STdCSjRvbXlzNVMxOXVmSlBCSgpJellqZU83b1RW -cUkzN0Y2RVVtalpxRzRXVkUyVVFiUURrb3NaYlpOODJPNElwdTFsRkFQRWJ3anFlUE1LdWZ6CnNu -U1FIS2ZuYnl5RFBFVk5sSmJzMTlOWEM4djZnK3BRYXk1ckgvSTZOMmlCeGdzVG11ZW1aNTRFaE5R -TVp5RU4KUi9DaWhlQXJXRUg5SDgvNGhkMmdjOVRiMnMwTXdHSElMTDRrYmJObTV0cDN4dzRpazdP -WVdOcmozbStuRzZYYgp2S1hoMnhFYW5BWkF5TVhUcURKVEhkbjcvQ0VxdXNRUEpqWkdWK01mMWtq -S3U3cDRxY1hGbklYUDVJTG5UVzdiCmxIb1dDNGV3ZUR6S09NUnpYbWJBQkVWU1V2eDJTbVBsNFRj -b0M1TDFTQ0FIRW1aYUtiYVk3UzVsNTN1NmdsMGYKVUx1UWJ0N0hyM1RIem5sTkZLa0dUMS95Vk50 -MlFPbTFlbVpkNTVMYU5lOEU3WHNOU2xobDBncllRK1VlOEpiYQp4ODVPYXBsdFZqeE05d1ZDd2Jn -RnlpMDRpaGRLSG85ZSt1WUtlVEdLdjBoVTVPN0hFSDFldjZ0L3MydS9VRzZoClRxRXNZclZwMENN -SHB0NXVBRjZuWnlLNkdaL0NIVHhoL3J6MWhBRE1vZmVtNTkrZTZ0VnRqblBHQTNFam5KVDgKQk1P -dy9EMlFJRHhqeGoyR1V6eitZSnA1MEVOaFdyTDlvU0RrRzJuenY0TlZMNzdRSXkrVC8yL2Y0UGdv -a1VETwpRSmpJZnhQV0U0MGNIR0hwblF0WnZFUG94UDBIM1QwWWhtRVZ3dUp4WDN1YVdPWS84RmEx -YzdMbjBTd1dkZlY1CmdZdkpWOG82YzNzdW1jcTFPM2FnUERsSEM1TzRJeEc3QVpROENIUkR5QVNv -Z3pma1k2UDU3OVpPR1lhTzRhbDcKV0ExWUlwc0hzMy8xZjRTQnlNdVdlME5Wa0Zmdlhja2pwcUdy -QlFwVG1xUXprNmJhYTBWUTBjd1UzWGxrd0hhYwpXQi9mUTRqeWx3RnpaRGNwNUpBbzUzbjZhVTcy -emdOdkRsR1ROS3dkWFhaSTVVM0pQb2NIMEFpWmdGRldZSkxkCjYzUEpMRG5qeUUzaTZYTVZseGlm -WEtrWFZ2MFJZU3orQnlTN096OWFDZ25RaE5VOHljditVeHRma1BRaWg1ekUKLzBZMkVFRmtuYWpt -RkpwTlhjenpGOE9FemFzd21SMEFPamNDaWtsWktSZjYxcmY1ZmFKeEpoaHFLRUVCSnVMNgpvb2RE -VlJrM09HVTF5UVNCYXpUOG5LM1YrZTZGTW8zdFdrcmEyQlhGQ0QrcEt4VHkwMTRDcDU5UzF3NkYx -Rmp0CldYN2VNV1NMV2ZRNTZqMmtMTUJIcTVnYjJhcnFscUgzZnNZT1REM1ROakNZRjNTZ3gzMDlr -VlB1T0s1dnc2MVAKcG5ML0xOM2lHWTQyV1IrOWxmQXlOTjJxajl6dndLd3NjeVlzNStEUFFvUG1j -UGNWR2Mzdi91NjZiTGNPR2JFVQpPbEdhLzZnZEQ0R0NwNUU0ZlAvN0dibkVZL1BXMmFicXVGaEdC -K3BWZGwzLzQrMVUvOGtJdGxmV05ab0c0RmhFCmdqTWQ3Z2xtcmRGaU5KRkZwZjVrczFsVlhHcUo0 -bVp4cXRFWnJ4VUV3Y2laam00VjI3YStFMkt5VjlObmtzWjYKeEY0dEdQS0lQc3ZOVFY1bzhacWpp -YWN4Z2JZbXIyeXdxRFhLQ2dwVS9SV1NoMXNMYXBxU1FxYkgvdzBNcXVVagpWaFZYMFJNWUgvZm9L -dGphZ1pmL0tPMS9tbkNJVGw4NnRyZUlkYWNoR2dSNHdyL3FxTWpycFBVYVBMQ1JZM0pRCjAwWFVQ -MU11NllQRTBTbk1ZQVZ4WmhlcUtIbHkzYTFwZzRYcDdZV2xNNjcxb1VPUnMzK1ZFTmZuYkl4Z3Ir -MkQKVGlKVDlQeHdwZks1M09oN1JCU1dISlpSdUFkTFVYRThERytibDBOL1FrSk02cEZVeFRJMUFR -PT0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K` - - return base64.StdEncoding.DecodeString(s) -} - -func publicbytes() ([]byte, error) { - s := `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvekNDQWVlZ0F3SUJBZ0lCQVRBTkJna3Fo -a2lHOXcwQkFRVUZBREFhTVFzd0NRWURWUVFHRXdKVlV6RUwKTUFrR0ExVUVDZ3dDV2pRd0hoY05N -VE13T0RJNE1UZ3lPRE0wV2hjTk1qTXdPREk0TVRneU9ETTBXakFhTVFzdwpDUVlEVlFRR0V3SlZV -ekVMTUFrR0ExVUVDZ3dDV2pRd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3CmdnRUtB -b0lCQVFEZmRPcW90SGQ1NVNZTzBkTHoyb1hlbmd3L3RaK3EzWm1PUGVWbU11T01JWU8vQ3Yxd2sy -VTAKT0s0cHVnNE9CU0pQaGwwOVpzNkl3QjhOd1BPVTdFRFRnTU9jUVVZQi82UU5DSTFKN1ptMm9M -dHVjaHp6NHBJYgorbzRaQWhWcHJMaFJ5dnFpOE9US1E3a2ZHZnM1VHV3bW4xTS8wZlFrZnpNeEFE -cGpPS05nZjB1eTZsTjZ1dGpkClRyUEtLRlVRTmRjNi9UeThFZVRuUUV3VWxzVDJMQVhDZkVLeFRu -NVJsUmxqRHp0UzdTZmdzOFZMMEZQeTFRaTgKQitkRmNnUllLRnJjcHNWYVoxbEJtWEtzWERSdTVR -Ui9SZzNmOURScTRHUjFzTkg4UkxZOXVBcE1sMlNOeitzUgo0elJQRzg1Ui9zZTVRMDZHdTBCVVEz -VVBtNjdFVFZaTEFnTUJBQUdqVURCT01CMEdBMVVkRGdRV0JCUUhaUFRFCnlRVnUvMEkvM1FXaGxU -eVc3V29UelRBZkJnTlZIU01FR0RBV2dCUUhaUFRFeVFWdS8wSS8zUVdobFR5VzdXb1QKelRBTUJn -TlZIUk1FQlRBREFRSC9NQTBHQ1NxR1NJYjNEUUVCQlFVQUE0SUJBUURIeHFKOXk4YWxUSDdhZ1ZN -VwpaZmljL1JicmR2SHd5cStJT3JnRFRvcXlvMHcrSVo2QkNuOXZqdjVpdWhxdTRGb3JPV0RBRnBR -S1pXMERMQkpFClF5LzcvMCs5cGsyRFBoSzFYemRPb3ZsU3JrUnQrR2NFcEduVVhuekFDWERCYk8w -K1dyaytoY2pFa1FSUksxYlcKMnJrbkFSSUVKRzlHUytwU2hQOUJxLzBCbU5zTWVwZE5jQmEwejNh -NUIwZnpGeUNRb1VsWDZSVHF4UncxaDFRdAo1RjAwcGZzcDdTalhWSXZZY2V3SGFOQVNidG8xbjVo -clN6MVZZOWhMYmExMWl2TDFONFdvV2JtekFMNkJXYWJzCkMyRC9NZW5TVDIvWDZoVEt5R1hwZzNF -ZzJoM2lMdlV0d2NObnkwaFJLc3RjNzNKbDl4UjNxWGZYS0pIMFRoVGwKcTBncQotLS0tLUVORCBD -RVJUSUZJQ0FURS0tLS0tCg==` - return base64.StdEncoding.DecodeString(s) -} diff --git a/test/integration/accessTokens_test.go b/test/integration/accessTokens_test.go index 73cbf0e..42fee2a 100644 --- a/test/integration/accessTokens_test.go +++ b/test/integration/accessTokens_test.go @@ -20,24 +20,24 @@ func TestAccessTokenGet(t *testing.T) { ctx := h.NewAuthenticatedContext(account) // 401 using no JWT token - _, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1AccessTokensIdGet(context.Background(), "foo").Execute() + _, _, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1AccessTokensIdGet(context.Background(), "foo").Execute() Expect(err).To(HaveOccurred(), "Expected 401 but got nil error") // GET responses per openapi spec: 200 and 404, - _, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1AccessTokensIdGet(ctx, "foo").Execute() + _, resp, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1AccessTokensIdGet(ctx, "foo").Execute() Expect(err).To(HaveOccurred(), "Expected 404") Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) accessTokenModel, err := h.Factories.NewAccessTokens(h.NewID()) Expect(err).NotTo(HaveOccurred()) - accessTokenOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1AccessTokensIdGet(ctx, accessTokenModel.ID).Execute() + accessTokenOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1AccessTokensIdGet(ctx, accessTokenModel.ID).Execute() Expect(err).NotTo(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusOK)) Expect(*accessTokenOutput.Id).To(Equal(accessTokenModel.ID), "found object does not match test object") Expect(*accessTokenOutput.Kind).To(Equal("AccessToken")) - Expect(*accessTokenOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credential-service/v1/access_tokens/%s", accessTokenModel.ID))) + Expect(*accessTokenOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credentials-service/v1/access_tokens/%s", accessTokenModel.ID))) Expect(*accessTokenOutput.CreatedAt).To(BeTemporally("~", accessTokenModel.CreatedAt)) Expect(*accessTokenOutput.UpdatedAt).To(BeTemporally("~", accessTokenModel.UpdatedAt)) } @@ -52,12 +52,12 @@ func TestAccessTokenPost(t *testing.T) { accessTokenInput := openapi.AccessToken{} // 201 Created - accessTokenOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1AccessTokensPost(ctx).AccessToken(accessTokenInput).Execute() + accessTokenOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1AccessTokensPost(ctx).AccessToken(accessTokenInput).Execute() Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) Expect(resp.StatusCode).To(Equal(http.StatusCreated)) Expect(*accessTokenOutput.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") Expect(*accessTokenOutput.Kind).To(Equal("AccessToken")) - Expect(*accessTokenOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credential-service/v1/access_tokens/%s", *accessTokenOutput.Id))) + Expect(*accessTokenOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credentials-service/v1/access_tokens/%s", *accessTokenOutput.Id))) // 400 bad request. posting junk json is one way to trigger 400. jwtToken := ctx.Value(openapi.ContextAccessToken) @@ -82,13 +82,13 @@ func TestAccessTokenPatch(t *testing.T) { Expect(err).NotTo(HaveOccurred()) // 200 OK - accessTokenOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1AccessTokensIdPatch(ctx, accessTokenModel.ID).AccessTokenPatchRequest(openapi.AccessTokenPatchRequest{}).Execute() + accessTokenOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1AccessTokensIdPatch(ctx, accessTokenModel.ID).AccessTokenPatchRequest(openapi.AccessTokenPatchRequest{}).Execute() Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) Expect(resp.StatusCode).To(Equal(http.StatusOK)) Expect(*accessTokenOutput.Id).To(Equal(accessTokenModel.ID)) Expect(*accessTokenOutput.CreatedAt).To(BeTemporally("~", accessTokenModel.CreatedAt)) Expect(*accessTokenOutput.Kind).To(Equal("AccessToken")) - Expect(*accessTokenOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credential-service/v1/access_tokens/%s", *accessTokenOutput.Id))) + Expect(*accessTokenOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credentials-service/v1/access_tokens/%s", *accessTokenOutput.Id))) jwtToken := ctx.Value(openapi.ContextAccessToken) // 500 server error. posting junk json is one way to trigger 500. @@ -111,14 +111,14 @@ func TestAccessTokenPaging(t *testing.T) { _, err := h.Factories.NewAccessTokensList("Bronto", 20) Expect(err).NotTo(HaveOccurred()) - list, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1AccessTokensGet(ctx).Execute() + list, _, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1AccessTokensGet(ctx).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting accessToken list: %v", err) Expect(len(list.Items)).To(Equal(20)) Expect(list.Size).To(Equal(int32(20))) Expect(list.Total).To(Equal(int32(20))) Expect(list.Page).To(Equal(int32(1))) - list, _, err = client.DefaultAPI.ApiRegistryCredentialServiceV1AccessTokensGet(ctx).Page(2).Size(5).Execute() + list, _, err = client.DefaultAPI.ApiRegistryCredentialsServiceV1AccessTokensGet(ctx).Page(2).Size(5).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting accessToken list: %v", err) Expect(len(list.Items)).To(Equal(5)) Expect(list.Size).To(Equal(int32(5))) @@ -135,7 +135,7 @@ func TestAccessTokenListSearch(t *testing.T) { accessTokens, _ := h.Factories.NewAccessTokensList("bronto", 20) search := fmt.Sprintf("id in ('%s')", accessTokens[0].ID) - list, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1AccessTokensGet(ctx).Search(search).Execute() + list, _, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1AccessTokensGet(ctx).Search(search).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting accessToken list: %v", err) Expect(len(list.Items)).To(Equal(1)) Expect(list.Total).To(Equal(int32(1))) diff --git a/test/integration/controller_test.go b/test/integration/controller_test.go index e9ce99b..b4c5acc 100755 --- a/test/integration/controller_test.go +++ b/test/integration/controller_test.go @@ -7,13 +7,13 @@ import ( "time" . "github.com/onsi/gomega" - "github.com/openshift-hyperfleet/registry-credentials-service/cmd/registry-credentials-service/server" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/controllers" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/db" - "github.com/openshift-hyperfleet/registry-credentials-service/plugins/events" "github.com/openshift-hyperfleet/registry-credentials-service/test" + "github.com/openshift-online/rh-trex/pkg/api" + "github.com/openshift-online/rh-trex/pkg/controllers" + "github.com/openshift-online/rh-trex/pkg/dao" + "github.com/openshift-online/rh-trex/pkg/db" + pkgserver "github.com/openshift-online/rh-trex/pkg/server" + "github.com/openshift-online/rh-trex/plugins/events" ) func TestControllerRacing(t *testing.T) { @@ -23,9 +23,6 @@ func TestControllerRacing(t *testing.T) { authCtx := h.NewAuthenticatedContext(account) dao := dao.NewEventDao(&h.Env().Database.SessionFactory) - // The handler filters the events by source id/type/reconciled, and only record - // the event with create type. Due to the event lock, each create event - // should be only processed once. var proccessedEvent []string onUpsert := func(ctx context.Context, id string) error { events, err := dao.All(authCtx) @@ -40,7 +37,6 @@ func TestControllerRacing(t *testing.T) { if evt.EventType != api.CreateEventType { continue } - // the event has been reconciled by others, ignore. if evt.ReconciledDate != nil { continue } @@ -50,19 +46,19 @@ func TestControllerRacing(t *testing.T) { return nil } - // Start 3 controllers concurrently threads := 3 for i := 0; i < threads; i++ { go func() { - s := &server.ControllersServer{ + s := &pkgserver.ControllersServer{ KindControllerManager: controllers.NewKindControllerManager( db.NewAdvisoryLockFactory(h.Env().Database.SessionFactory), events.Service(&h.Env().Services), ), + SessionFactory: h.Env().Database.SessionFactory, } s.KindControllerManager.Add(&controllers.ControllerConfig{ - Source: "Dinosaurs", + Source: "RegistryCredentials", Handlers: map[api.EventType][]controllers.ControllerHandlerFunc{ api.CreateEventType: {onUpsert}, api.UpdateEventType: {onUpsert}, @@ -73,17 +69,14 @@ func TestControllerRacing(t *testing.T) { }() } - // make some time for the controllers to start, or they will miss events time.Sleep(100 * time.Millisecond) const N = 50 - dinos, err := h.Factories.NewDinosaurList("bronto", N) + creds, err := h.Factories.NewRegistryCredentialsList("test", N) Expect(err).NotTo(HaveOccurred()) - Expect(len(dinos)).To(Equal(N)) + Expect(len(creds)).To(Equal(N)) - // This is to check only two create events is processed. It waits for 5 seconds to ensure all events have been - // processed by the controllers. Eventually(func() error { if len(proccessedEvent) != N { return fmt.Errorf("should have %d create events but got %d", N, len(proccessedEvent)) diff --git a/test/integration/dinosaurs_test.go b/test/integration/dinosaurs_test.go deleted file mode 100755 index 3349983..0000000 --- a/test/integration/dinosaurs_test.go +++ /dev/null @@ -1,282 +0,0 @@ -package integration - -import ( - "context" - "fmt" - "net/http" - "sync" - "testing" - "time" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/dao" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/services" - - . "github.com/onsi/gomega" - "gopkg.in/resty.v1" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api/openapi" - "github.com/openshift-hyperfleet/registry-credentials-service/test" -) - -func TestDinosaurGet(t *testing.T) { - h, client := test.RegisterIntegration(t) - - account := h.NewRandAccount() - ctx := h.NewAuthenticatedContext(account) - - // 401 using no JWT token - _, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1DinosaursIdGet(context.Background(), "foo").Execute() - Expect(err).To(HaveOccurred(), "Expected 401 but got nil error") - - // GET responses per openapi spec: 200 and 404, - _, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1DinosaursIdGet(ctx, "foo").Execute() - Expect(err).To(HaveOccurred(), "Expected 404") - Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) - - dino, err := h.Factories.NewDinosaur(h.NewID()) - Expect(err).NotTo(HaveOccurred()) - - dinosaur, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1DinosaursIdGet(ctx, dino.ID).Execute() - Expect(err).NotTo(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - - Expect(*dinosaur.Id).To(Equal(dino.ID), "found object does not match test object") - Expect(dinosaur.Species).To(Equal(dino.Species), "species mismatch") - Expect(*dinosaur.Kind).To(Equal("Dinosaur")) - Expect(*dinosaur.Href).To(Equal(fmt.Sprintf("/api/registry-credential-service/v1/dinosaurs/%s", dino.ID))) - Expect(*dinosaur.CreatedAt).To(BeTemporally("~", dino.CreatedAt)) - Expect(*dinosaur.UpdatedAt).To(BeTemporally("~", dino.UpdatedAt)) -} - -func TestDinosaurPost(t *testing.T) { - h, client := test.RegisterIntegration(t) - - account := h.NewRandAccount() - ctx := h.NewAuthenticatedContext(account) - - // POST responses per openapi spec: 201, 409, 500 - dino := openapi.Dinosaur{ - Species: time.Now().String(), - } - - // 201 Created - dinosaur, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1DinosaursPost(ctx).Dinosaur(dino).Execute() - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*dinosaur.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") - Expect(dinosaur.Species).To(Equal(dino.Species), "species mismatch") - Expect(*dinosaur.Kind).To(Equal("Dinosaur")) - Expect(*dinosaur.Href).To(Equal(fmt.Sprintf("/api/registry-credential-service/v1/dinosaurs/%s", *dinosaur.Id))) - - // 400 bad request. posting junk json is one way to trigger 400. - jwtToken := ctx.Value(openapi.ContextAccessToken) - restyResp, err := resty.R(). - SetHeader("Content-Type", "application/json"). - SetHeader("Authorization", fmt.Sprintf("Bearer %s", jwtToken)). - SetBody(`{ this is invalid }`). - Post(h.RestURL("/dinosaurs")) - - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - Expect(restyResp.StatusCode()).To(Equal(http.StatusBadRequest)) -} - -func TestDinosaurPatch(t *testing.T) { - h, client := test.RegisterIntegration(t) - - account := h.NewRandAccount() - ctx := h.NewAuthenticatedContext(account) - - // POST responses per openapi spec: 201, 409, 500 - - dino, err := h.Factories.NewDinosaur("Brontosaurus") - Expect(err).NotTo(HaveOccurred()) - - // 200 OK - species := "Dodo" - dinosaur, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1DinosaursIdPatch(ctx, dino.ID).DinosaurPatchRequest(openapi.DinosaurPatchRequest{Species: &species}).Execute() - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*dinosaur.Id).To(Equal(dino.ID)) - Expect(dinosaur.Species).To(Equal(species), "species mismatch") - Expect(*dinosaur.CreatedAt).To(BeTemporally("~", dino.CreatedAt)) - Expect(*dinosaur.Kind).To(Equal("Dinosaur")) - Expect(*dinosaur.Href).To(Equal(fmt.Sprintf("/api/registry-credential-service/v1/dinosaurs/%s", *dinosaur.Id))) - - jwtToken := ctx.Value(openapi.ContextAccessToken) - // 500 server error. posting junk json is one way to trigger 500. - restyResp, err := resty.R(). - SetHeader("Content-Type", "application/json"). - SetHeader("Authorization", fmt.Sprintf("Bearer %s", jwtToken)). - SetBody(`{ this is invalid }`). - Patch(h.RestURL("/dinosaurs/foo")) - - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - Expect(restyResp.StatusCode()).To(Equal(http.StatusBadRequest)) - - // species can not be empty in request body - restyResp, err = resty.R(). - SetHeader("Content-Type", "application/json"). - SetHeader("Authorization", fmt.Sprintf("Bearer %s", jwtToken)). - SetBody(`{"species":""}`). - Patch(h.RestURL(fmt.Sprintf("/dinosaurs/%s", *dinosaur.Id))) - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - Expect(restyResp.StatusCode()).To(Equal(http.StatusBadRequest)) - Expect(restyResp.String()).To(ContainSubstring("species cannot be empty")) - - Eventually(func() error { - dao := dao.NewEventDao(&h.Env().Database.SessionFactory) - events, err := dao.FindByIDs(ctx, []string{*dinosaur.Id}) - Expect(err).NotTo(HaveOccurred(), "Error getting events: %v", err) - Expect(len(events)).To(Equal(2), "expected Create and Update events") - Expect(contains(api.CreateEventType, events)).To(BeTrue()) - Expect(contains(api.UpdateEventType, events)).To(BeTrue()) - return nil - }, 5*time.Second, 1*time.Second) -} - -func contains(et api.EventType, events api.EventList) bool { - for _, e := range events { - if e.EventType == et { - return true - } - } - return false -} - -func TestDinosaurPaging(t *testing.T) { - h, client := test.RegisterIntegration(t) - - account := h.NewRandAccount() - ctx := h.NewAuthenticatedContext(account) - - // Paging - _, err := h.Factories.NewDinosaurList("Bronto", 20) - Expect(err).NotTo(HaveOccurred()) - - list, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1DinosaursGet(ctx).Execute() - Expect(err).NotTo(HaveOccurred(), "Error getting dinosaur list: %v", err) - Expect(len(list.Items)).To(Equal(20)) - Expect(list.Size).To(Equal(int32(20))) - Expect(list.Total).To(Equal(int32(20))) - Expect(list.Page).To(Equal(int32(1))) - - list, _, err = client.DefaultAPI.ApiRegistryCredentialServiceV1DinosaursGet(ctx).Page(2).Size(5).Execute() - Expect(err).NotTo(HaveOccurred(), "Error getting dinosaur list: %v", err) - Expect(len(list.Items)).To(Equal(5)) - Expect(list.Size).To(Equal(int32(5))) - Expect(list.Total).To(Equal(int32(20))) - Expect(list.Page).To(Equal(int32(2))) -} - -func TestDinosaurListSearch(t *testing.T) { - h, client := test.RegisterIntegration(t) - - account := h.NewRandAccount() - ctx := h.NewAuthenticatedContext(account) - - dinosaurs, err := h.Factories.NewDinosaurList("bronto", 20) - Expect(err).NotTo(HaveOccurred()) - - search := fmt.Sprintf("id in ('%s')", dinosaurs[0].ID) - list, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1DinosaursGet(ctx).Search(search).Execute() - Expect(err).NotTo(HaveOccurred(), "Error getting dinosaur list: %v", err) - Expect(len(list.Items)).To(Equal(1)) - Expect(list.Total).To(Equal(int32(1))) - Expect(*list.Items[0].Id).To(Equal(dinosaurs[0].ID)) -} - -func TestUpdateDinosaurWithRacingRequests_BlockingAdvisoryLock(t *testing.T) { - testUpdateDinosaurWithRacingRequests(t, true, true, 2) -} - -func TestUpdateDinosaurWithRacingRequests_NonBlockingAdvisoryLock(t *testing.T) { - testUpdateDinosaurWithRacingRequests(t, true, false, 1) -} - -func TestUpdateDinosaurWithRacingRequests_WithoutLock(t *testing.T) { - testUpdateDinosaurWithRacingRequests(t, false, false, 2) -} - -func testUpdateDinosaurWithRacingRequests(t *testing.T, useAdvisoryLock, useBlockingAdvisoryLock bool, expectedUpdates int) { - h, client := test.RegisterIntegration(t) - - services.DisableAdvisoryLock = !useAdvisoryLock - services.UseBlockingAdvisoryLock = useBlockingAdvisoryLock - - defer func() { - services.DisableAdvisoryLock = false - services.UseBlockingAdvisoryLock = true - }() - - account := h.NewRandAccount() - ctx := h.NewAuthenticatedContext(account) - - dino, err := h.Factories.NewDinosaur("Stegosaurus") - Expect(err).NotTo(HaveOccurred()) - - firstDinoUpdate := "AdvisoryLockosaurus" - secondDinoUpdate := "AdvisoryLockosaurusSecond" - var wg1 sync.WaitGroup - var wg2 sync.WaitGroup - wg1.Add(1) - wg2.Add(2) - - // the call to update the dino with name==AdvisoryLockosaurus take 1second in the service, since it has hardcoded an special case - go func() { - wg1.Done() - client.DefaultAPI.ApiRegistryCredentialServiceV1DinosaursIdPatch(ctx, dino.ID).DinosaurPatchRequest(openapi.DinosaurPatchRequest{Species: &firstDinoUpdate}).Execute() - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - wg2.Done() - }() - - // we need the gorutine to have sent the first update - // there may be some uncertainty, since the first API call should have been fired, but may or may not have reached the server yet - // so we wait additional time - wg1.Wait() - time.Sleep(100 * time.Millisecond) - - go func() { - client.DefaultAPI.ApiRegistryCredentialServiceV1DinosaursIdPatch(ctx, dino.ID).DinosaurPatchRequest(openapi.DinosaurPatchRequest{Species: &secondDinoUpdate}).Execute() - wg2.Done() - }() - - // waits for all goroutines above to complete - wg2.Wait() - - eventdao := dao.NewEventDao(&h.Env().Database.SessionFactory) - events, err := eventdao.All(ctx) - Expect(err).NotTo(HaveOccurred(), "Error getting events: %v", err) - - dinodao := dao.NewDinosaurDao(&h.Env().Database.SessionFactory) - readDino, err := dinodao.Get(ctx, dino.ID) - if useBlockingAdvisoryLock { - Expect(readDino.Species).To(Equal(secondDinoUpdate)) - } else { - Expect(readDino.Species).To(Equal(firstDinoUpdate)) - } - - updatedCount := 0 - for _, e := range events { - if e.SourceID == dino.ID && e.EventType == api.UpdateEventType { - updatedCount = updatedCount + 1 - } - } - - // the dinosaur patch request is protected by the advisory lock, so there should only be one update - Expect(updatedCount).To(Equal(expectedUpdates)) - - // all the locks should be released finally - Eventually(func() error { - var count int - err := h.DBFactory.DirectDB(). - QueryRow("select count(*) from pg_locks where locktype='advisory';"). - Scan(&count) - Expect(err).NotTo(HaveOccurred(), "Error querying pg_locks: %v", err) - - if count != 0 { - return fmt.Errorf("there are %d unreleased advisory lock", count) - } - return nil - }, 5*time.Second, 1*time.Second).Should(Succeed()) -} diff --git a/test/integration/metadata_test.go b/test/integration/metadata_test.go index bb1a747..6223b14 100755 --- a/test/integration/metadata_test.go +++ b/test/integration/metadata_test.go @@ -25,21 +25,21 @@ import ( . "github.com/onsi/gomega" "gopkg.in/resty.v1" - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/api" "github.com/openshift-hyperfleet/registry-credentials-service/test" + "github.com/openshift-online/rh-trex/pkg/api" ) func TestMetadataGet(t *testing.T) { h, _ := test.RegisterIntegration(t) - // Build the metadata URL (metadata endpoint is at /api/registry-credential-service, not /api/registry-credential-service/v1) + // Build the metadata URL (metadata endpoint is at /api/registry-credentials-service, not /api/registry-credentials-service/v1) protocol := "http" if h.AppConfig.Server.EnableHTTPS { protocol = "https" } - metadataURL := fmt.Sprintf("%s://%s/api/registry-credential-service", protocol, h.AppConfig.Server.BindAddress) + metadataURL := fmt.Sprintf("%s://%s/api/registry-credentials-service", protocol, h.AppConfig.Server.BindAddress) - // Test GET /api/registry-credential-service - metadata endpoint doesn't require authentication + // Test GET /api/registry-credentials-service - metadata endpoint doesn't require authentication resp, err := resty.R(). SetHeader("Content-Type", "application/json"). Get(metadataURL) @@ -57,9 +57,9 @@ func TestMetadataGet(t *testing.T) { Expect(contentType).To(Equal("application/json"), "Expected Content-Type to be application/json") // Verify all metadata fields - Expect(metadata.ID).To(Equal("registry-credential-service"), "Expected ID to be 'registry-credential-service'") + Expect(metadata.ID).To(Equal("registry-credentials-service"), "Expected ID to be 'registry-credentials-service'") Expect(metadata.Kind).To(Equal("API"), "Expected Kind to be 'API'") - Expect(metadata.HREF).To(Equal("/api/registry-credential-service"), "Expected HREF to match the request path") + Expect(metadata.HREF).To(Equal("/api/registry-credentials-service"), "Expected HREF to match the request path") Expect(metadata.Version).NotTo(BeEmpty(), "Expected Version to be set") Expect(metadata.BuildTime).NotTo(BeEmpty(), "Expected BuildTime to be set") } diff --git a/test/integration/openapi_test.go b/test/integration/openapi_test.go index 69b4ff9..e57bbfe 100755 --- a/test/integration/openapi_test.go +++ b/test/integration/openapi_test.go @@ -35,7 +35,7 @@ func TestOpenAPIGet(t *testing.T) { if h.AppConfig.Server.EnableHTTPS { protocol = "https" } - openAPIURL := fmt.Sprintf("%s://%s/api/registry-credential-service/v1/openapi", protocol, h.AppConfig.Server.BindAddress) + openAPIURL := fmt.Sprintf("%s://%s/api/registry-credentials-service/v1/openapi", protocol, h.AppConfig.Server.BindAddress) resp, err := resty.R(). SetHeader("Content-Type", "application/json"). @@ -75,7 +75,7 @@ func TestOpenAPIUIGet(t *testing.T) { if h.AppConfig.Server.EnableHTTPS { protocol = "https" } - openAPIUIURL := fmt.Sprintf("%s://%s/api/registry-credential-service/v1/openapi.html", protocol, h.AppConfig.Server.BindAddress) + openAPIUIURL := fmt.Sprintf("%s://%s/api/registry-credentials-service/v1/openapi.html", protocol, h.AppConfig.Server.BindAddress) t.Logf("OpenAPI UI URL: %s", openAPIUIURL) diff --git a/test/integration/registryCredentials_test.go b/test/integration/registryCredentials_test.go index a8894a9..269af02 100644 --- a/test/integration/registryCredentials_test.go +++ b/test/integration/registryCredentials_test.go @@ -20,24 +20,24 @@ func TestRegistryCredentialGet(t *testing.T) { ctx := h.NewAuthenticatedContext(account) // 401 using no JWT token - _, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistryCredentialsIdGet(context.Background(), "foo").Execute() + _, _, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistryCredentialsIdGet(context.Background(), "foo").Execute() Expect(err).To(HaveOccurred(), "Expected 401 but got nil error") // GET responses per openapi spec: 200 and 404, - _, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistryCredentialsIdGet(ctx, "foo").Execute() + _, resp, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistryCredentialsIdGet(ctx, "foo").Execute() Expect(err).To(HaveOccurred(), "Expected 404") Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) registryCredentialModel, err := h.Factories.NewRegistryCredentials(h.NewID()) Expect(err).NotTo(HaveOccurred()) - registryCredentialOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistryCredentialsIdGet(ctx, registryCredentialModel.ID).Execute() + registryCredentialOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistryCredentialsIdGet(ctx, registryCredentialModel.ID).Execute() Expect(err).NotTo(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusOK)) Expect(*registryCredentialOutput.Id).To(Equal(registryCredentialModel.ID), "found object does not match test object") Expect(*registryCredentialOutput.Kind).To(Equal("RegistryCredential")) - Expect(*registryCredentialOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credential-service/v1/registry_credentials/%s", registryCredentialModel.ID))) + Expect(*registryCredentialOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credentials-service/v1/registry_credentials/%s", registryCredentialModel.ID))) Expect(*registryCredentialOutput.CreatedAt).To(BeTemporally("~", registryCredentialModel.CreatedAt)) Expect(*registryCredentialOutput.UpdatedAt).To(BeTemporally("~", registryCredentialModel.UpdatedAt)) } @@ -52,12 +52,12 @@ func TestRegistryCredentialPost(t *testing.T) { registryCredentialInput := openapi.RegistryCredential{} // 201 Created - registryCredentialOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistryCredentialsPost(ctx).RegistryCredential(registryCredentialInput).Execute() + registryCredentialOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistryCredentialsPost(ctx).RegistryCredential(registryCredentialInput).Execute() Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) Expect(resp.StatusCode).To(Equal(http.StatusCreated)) Expect(*registryCredentialOutput.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") Expect(*registryCredentialOutput.Kind).To(Equal("RegistryCredential")) - Expect(*registryCredentialOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credential-service/v1/registry_credentials/%s", *registryCredentialOutput.Id))) + Expect(*registryCredentialOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credentials-service/v1/registry_credentials/%s", *registryCredentialOutput.Id))) // 400 bad request. posting junk json is one way to trigger 400. jwtToken := ctx.Value(openapi.ContextAccessToken) @@ -82,13 +82,13 @@ func TestRegistryCredentialPatch(t *testing.T) { Expect(err).NotTo(HaveOccurred()) // 200 OK - registryCredentialOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistryCredentialsIdPatch(ctx, registryCredentialModel.ID).RegistryCredentialPatchRequest(openapi.RegistryCredentialPatchRequest{}).Execute() + registryCredentialOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistryCredentialsIdPatch(ctx, registryCredentialModel.ID).RegistryCredentialPatchRequest(openapi.RegistryCredentialPatchRequest{}).Execute() Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) Expect(resp.StatusCode).To(Equal(http.StatusOK)) Expect(*registryCredentialOutput.Id).To(Equal(registryCredentialModel.ID)) Expect(*registryCredentialOutput.CreatedAt).To(BeTemporally("~", registryCredentialModel.CreatedAt)) Expect(*registryCredentialOutput.Kind).To(Equal("RegistryCredential")) - Expect(*registryCredentialOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credential-service/v1/registry_credentials/%s", *registryCredentialOutput.Id))) + Expect(*registryCredentialOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credentials-service/v1/registry_credentials/%s", *registryCredentialOutput.Id))) jwtToken := ctx.Value(openapi.ContextAccessToken) // 500 server error. posting junk json is one way to trigger 500. @@ -111,14 +111,14 @@ func TestRegistryCredentialPaging(t *testing.T) { _, err := h.Factories.NewRegistryCredentialsList("Bronto", 20) Expect(err).NotTo(HaveOccurred()) - list, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistryCredentialsGet(ctx).Execute() + list, _, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistryCredentialsGet(ctx).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting registryCredential list: %v", err) Expect(len(list.Items)).To(Equal(20)) Expect(list.Size).To(Equal(int32(20))) Expect(list.Total).To(Equal(int32(20))) Expect(list.Page).To(Equal(int32(1))) - list, _, err = client.DefaultAPI.ApiRegistryCredentialServiceV1RegistryCredentialsGet(ctx).Page(2).Size(5).Execute() + list, _, err = client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistryCredentialsGet(ctx).Page(2).Size(5).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting registryCredential list: %v", err) Expect(len(list.Items)).To(Equal(5)) Expect(list.Size).To(Equal(int32(5))) @@ -135,7 +135,7 @@ func TestRegistryCredentialListSearch(t *testing.T) { registryCredentials, _ := h.Factories.NewRegistryCredentialsList("bronto", 20) search := fmt.Sprintf("id in ('%s')", registryCredentials[0].ID) - list, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistryCredentialsGet(ctx).Search(search).Execute() + list, _, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistryCredentialsGet(ctx).Search(search).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting registryCredential list: %v", err) Expect(len(list.Items)).To(Equal(1)) Expect(list.Total).To(Equal(int32(1))) diff --git a/test/integration/registrys_test.go b/test/integration/registrys_test.go index cae9b30..2c2d9db 100644 --- a/test/integration/registrys_test.go +++ b/test/integration/registrys_test.go @@ -20,24 +20,24 @@ func TestRegistryGet(t *testing.T) { ctx := h.NewAuthenticatedContext(account) // 401 using no JWT token - _, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistrysIdGet(context.Background(), "foo").Execute() + _, _, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistrysIdGet(context.Background(), "foo").Execute() Expect(err).To(HaveOccurred(), "Expected 401 but got nil error") // GET responses per openapi spec: 200 and 404, - _, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistrysIdGet(ctx, "foo").Execute() + _, resp, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistrysIdGet(ctx, "foo").Execute() Expect(err).To(HaveOccurred(), "Expected 404") Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) registryModel, err := h.Factories.NewRegistrys(h.NewID()) Expect(err).NotTo(HaveOccurred()) - registryOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistrysIdGet(ctx, registryModel.ID).Execute() + registryOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistrysIdGet(ctx, registryModel.ID).Execute() Expect(err).NotTo(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusOK)) Expect(*registryOutput.Id).To(Equal(registryModel.ID), "found object does not match test object") Expect(*registryOutput.Kind).To(Equal("Registry")) - Expect(*registryOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credential-service/v1/registrys/%s", registryModel.ID))) + Expect(*registryOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credentials-service/v1/registrys/%s", registryModel.ID))) Expect(*registryOutput.CreatedAt).To(BeTemporally("~", registryModel.CreatedAt)) Expect(*registryOutput.UpdatedAt).To(BeTemporally("~", registryModel.UpdatedAt)) } @@ -52,12 +52,12 @@ func TestRegistryPost(t *testing.T) { registryInput := openapi.Registry{} // 201 Created - registryOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistrysPost(ctx).Registry(registryInput).Execute() + registryOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistrysPost(ctx).Registry(registryInput).Execute() Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) Expect(resp.StatusCode).To(Equal(http.StatusCreated)) Expect(*registryOutput.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") Expect(*registryOutput.Kind).To(Equal("Registry")) - Expect(*registryOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credential-service/v1/registrys/%s", *registryOutput.Id))) + Expect(*registryOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credentials-service/v1/registrys/%s", *registryOutput.Id))) // 400 bad request. posting junk json is one way to trigger 400. jwtToken := ctx.Value(openapi.ContextAccessToken) @@ -82,13 +82,13 @@ func TestRegistryPatch(t *testing.T) { Expect(err).NotTo(HaveOccurred()) // 200 OK - registryOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistrysIdPatch(ctx, registryModel.ID).RegistryPatchRequest(openapi.RegistryPatchRequest{}).Execute() + registryOutput, resp, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistrysIdPatch(ctx, registryModel.ID).RegistryPatchRequest(openapi.RegistryPatchRequest{}).Execute() Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) Expect(resp.StatusCode).To(Equal(http.StatusOK)) Expect(*registryOutput.Id).To(Equal(registryModel.ID)) Expect(*registryOutput.CreatedAt).To(BeTemporally("~", registryModel.CreatedAt)) Expect(*registryOutput.Kind).To(Equal("Registry")) - Expect(*registryOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credential-service/v1/registrys/%s", *registryOutput.Id))) + Expect(*registryOutput.Href).To(Equal(fmt.Sprintf("/api/registry-credentials-service/v1/registrys/%s", *registryOutput.Id))) jwtToken := ctx.Value(openapi.ContextAccessToken) // 500 server error. posting junk json is one way to trigger 500. @@ -111,14 +111,14 @@ func TestRegistryPaging(t *testing.T) { _, err := h.Factories.NewRegistrysList("Bronto", 20) Expect(err).NotTo(HaveOccurred()) - list, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistrysGet(ctx).Execute() + list, _, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistrysGet(ctx).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting registry list: %v", err) Expect(len(list.Items)).To(Equal(20)) Expect(list.Size).To(Equal(int32(20))) Expect(list.Total).To(Equal(int32(20))) Expect(list.Page).To(Equal(int32(1))) - list, _, err = client.DefaultAPI.ApiRegistryCredentialServiceV1RegistrysGet(ctx).Page(2).Size(5).Execute() + list, _, err = client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistrysGet(ctx).Page(2).Size(5).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting registry list: %v", err) Expect(len(list.Items)).To(Equal(5)) Expect(list.Size).To(Equal(int32(5))) @@ -135,7 +135,7 @@ func TestRegistryListSearch(t *testing.T) { registrys, _ := h.Factories.NewRegistrysList("bronto", 20) search := fmt.Sprintf("id in ('%s')", registrys[0].ID) - list, _, err := client.DefaultAPI.ApiRegistryCredentialServiceV1RegistrysGet(ctx).Search(search).Execute() + list, _, err := client.DefaultAPI.ApiRegistryCredentialsServiceV1RegistrysGet(ctx).Search(search).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting registry list: %v", err) Expect(len(list.Items)).To(Equal(1)) Expect(list.Total).To(Equal(int32(1))) diff --git a/test/mocks/jwk_cert_server.go b/test/mocks/jwk_cert_server.go deleted file mode 100755 index 17f47a2..0000000 --- a/test/mocks/jwk_cert_server.go +++ /dev/null @@ -1,46 +0,0 @@ -package mocks - -import ( - "crypto" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/mendsley/gojwk" -) - -const ( - certEndpoint = "/auth/realms/rhd/protocol/openid-connect/certs" -) - -func NewJWKCertServerMock(t *testing.T, pubKey crypto.PublicKey, jwkKID string, jwkAlg string) (url string, teardown func() error) { - certHandler := http.NewServeMux() - certHandler.HandleFunc(certEndpoint, - func(w http.ResponseWriter, r *http.Request) { - pubjwk, err := gojwk.PublicKey(pubKey) - if err != nil { - t.Errorf("Unable to generate public jwk: %s", err) - return - } - pubjwk.Kid = jwkKID - pubjwk.Alg = jwkAlg - jwkBytes, err := gojwk.Marshal(pubjwk) - if err != nil { - t.Errorf("Unable to marshal public jwk: %s", err) - return - } - fmt.Fprintf(w, fmt.Sprintf(`{"keys":[%s]}`, string(jwkBytes))) - }, - ) - - server := httptest.NewServer(certHandler) - return fmt.Sprintf("%s%s", server.URL, certEndpoint), serverClose(server) -} - -func serverClose(server *httptest.Server) func() error { - return func() error { - server.Close() - return nil - } -} diff --git a/test/mocks/mocks.go b/test/mocks/mocks.go deleted file mode 100755 index 4b64881..0000000 --- a/test/mocks/mocks.go +++ /dev/null @@ -1,19 +0,0 @@ -package mocks - -import ( - "net/http" - "net/http/httptest" - "time" -) - -// NewMockServerTimeout Returns a server that will wait waitTime when hit at endpoint -func NewMockServerTimeout(endpoint string, waitTime time.Duration) (*httptest.Server, func()) { - apiHandler := http.NewServeMux() - apiHandler.HandleFunc(endpoint, - func(w http.ResponseWriter, r *http.Request) { - time.Sleep(waitTime) - }, - ) - server := httptest.NewServer(apiHandler) - return server, server.Close -} diff --git a/test/mocks/ocm.go b/test/mocks/ocm.go deleted file mode 100755 index ec40967..0000000 --- a/test/mocks/ocm.go +++ /dev/null @@ -1,65 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/openshift-hyperfleet/registry-credentials-service/pkg/client/ocm" -) - -/* -The OCM Validator Mock will simply return true to all access_review requests instead -of reaching out to the AMS system or using the built-in OCM mock. It will record -the action and resourceType sent to it in the struct itself. This can be used -to validate that the expected action/resourceType for a particular endpoint was -determined in the authorization middleware - -Use: - h, client := test.RegisterIntegration(t) - authzMock, ocmMock := mocks.NewOCMAuthzValidatorMockClient() - // Use the OCM client mock, re-load services so they pick up the mock - h.Env().Clients.OCM = ocmMock - // The built-in mock has to be disabled or the server will use it instead - h.Env().Config.OCM.EnableMock = false - // Services and the server should be re-loaded to pick up the client with this mock - h.Env().LoadServices() - h.RestartServer() - - // Make a request, then validate the action and resourceType - Expect(authzMock.Action).To(Equal("get")) - Expect(authzMock.ResourceType).To(Equal("JQSJobQueue")) - authzMock.Reset() -*/ - -var _ ocm.Authorization = &OCMAuthzValidatorMock{} - -type OCMAuthzValidatorMock struct { - Action string - ResourceType string -} - -func NewOCMAuthzValidatorMockClient() (*OCMAuthzValidatorMock, *ocm.Client) { - authz := &OCMAuthzValidatorMock{ - Action: "", - ResourceType: "", - } - client := &ocm.Client{} - client.Authorization = authz - return authz, client -} - -func (m *OCMAuthzValidatorMock) SelfAccessReview(ctx context.Context, action, resourceType, organizationID, subscriptionID, clusterID string) (allowed bool, err error) { - m.Action = action - m.ResourceType = resourceType - return true, nil -} - -func (m *OCMAuthzValidatorMock) AccessReview(ctx context.Context, username, action, resourceType, organizationID, subscriptionID, clusterID string) (allowed bool, err error) { - m.Action = action - m.ResourceType = resourceType - return true, nil -} - -func (m OCMAuthzValidatorMock) Reset() { - m.Action = "" - m.ResourceType = "" -} diff --git a/test/registration.go b/test/registration.go index 17cd393..f000603 100755 --- a/test/registration.go +++ b/test/registration.go @@ -16,7 +16,7 @@ func RegisterIntegration(t *testing.T) (*Helper, *openapi.APIClient) { // Create a new helper helper := NewHelper(t) // Reset the database to a seeded blank state - helper.DBFactory.ResetDB() + helper.ResetDB() // Create an api client client := helper.NewApiClient() diff --git a/test/support/certs.json b/test/support/certs.json deleted file mode 100755 index 71cb126..0000000 --- a/test/support/certs.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "keys": [ - { - "kid": "HjYaVHwyM77lw0mv7ko-qC7tKri03jqSukNea0SWY7M", - "kty": "RSA", - "alg": "RS256", - "use": "sig", - "n": "q6DF0dZFJnnVVIUtyaVV9Hial9hsSRXtH8Z01kOoAdGwQLqFjKDzNeliOL9KL0i-D71Bo9vKp13Qo8r9UjjNPGV6HzxgXR95MIZP4nqWo9Qp_9SHOjxMSqg-ZFf45p0pSKRdgKTfzu0eJ1CpZt4BdYM9wM3iuOgon09hIMKcO0AU7xqX0KmCg-ToIgVDCaGtXqcC0qv3fr7acTUBoVd8sWNaIOKXiL90cR7oZX_wLoApF2cQyrgTozaMrdEe3RuvwU8hE_r3kYTUYsxTv0liJ8FRfuO5FJuEGVpYc7QDyIztt9YOqowQgHq_2IhqcWhULtzGIXh26voAgWfA2BGAFw", - "e": "AQAB" - } - ] -} diff --git a/test/support/jwt_ca.pem b/test/support/jwt_ca.pem deleted file mode 100755 index 458bcc5..0000000 --- a/test/support/jwt_ca.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC/zCCAeegAwIBAgIBATANBgkqhkiG9w0BAQUFADAaMQswCQYDVQQGEwJVUzEL -MAkGA1UECgwCWjQwHhcNMTMwODI4MTgyODM0WhcNMjMwODI4MTgyODM0WjAaMQsw -CQYDVQQGEwJVUzELMAkGA1UECgwCWjQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQDfdOqotHd55SYO0dLz2oXengw/tZ+q3ZmOPeVmMuOMIYO/Cv1wk2U0 -OK4pug4OBSJPhl09Zs6IwB8NwPOU7EDTgMOcQUYB/6QNCI1J7Zm2oLtuchzz4pIb -+o4ZAhVprLhRyvqi8OTKQ7kfGfs5Tuwmn1M/0fQkfzMxADpjOKNgf0uy6lN6utjd -TrPKKFUQNdc6/Ty8EeTnQEwUlsT2LAXCfEKxTn5RlRljDztS7Sfgs8VL0FPy1Qi8 -B+dFcgRYKFrcpsVaZ1lBmXKsXDRu5QR/Rg3f9DRq4GR1sNH8RLY9uApMl2SNz+sR -4zRPG85R/se5Q06Gu0BUQ3UPm67ETVZLAgMBAAGjUDBOMB0GA1UdDgQWBBQHZPTE -yQVu/0I/3QWhlTyW7WoTzTAfBgNVHSMEGDAWgBQHZPTEyQVu/0I/3QWhlTyW7WoT -zTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQDHxqJ9y8alTH7agVMW -Zfic/RbrdvHwyq+IOrgDToqyo0w+IZ6BCn9vjv5iuhqu4ForOWDAFpQKZW0DLBJE -Qy/7/0+9pk2DPhK1XzdOovlSrkRt+GcEpGnUXnzACXDBbO0+Wrk+hcjEkQRRK1bW -2rknARIEJG9GS+pShP9Bq/0BmNsMepdNcBa0z3a5B0fzFyCQoUlX6RTqxRw1h1Qt -5F00pfsp7SjXVIvYcewHaNASbto1n5hrSz1VY9hLba11ivL1N4WoWbmzAL6BWabs -C2D/MenST2/X6hTKyGXpg3Eg2h3iLvUtwcNny0hRKstc73Jl9xR3qXfXKJH0ThTl -q0gq ------END CERTIFICATE----- diff --git a/test/support/jwt_private_key.pem b/test/support/jwt_private_key.pem deleted file mode 100755 index 582119f..0000000 --- a/test/support/jwt_private_key.pem +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: DES-EDE3-CBC,2E65118E6C7B5207 - -7cYUTW4ZBdmVZ4ILB08hcTdm5ib0E0zcy+I7pHpNQfJHtI7BJ4omys5S19ufJPBJ -IzYjeO7oTVqI37F6EUmjZqG4WVE2UQbQDkosZbZN82O4Ipu1lFAPEbwjqePMKufz -snSQHKfnbyyDPEVNlJbs19NXC8v6g+pQay5rH/I6N2iBxgsTmuemZ54EhNQMZyEN -R/CiheArWEH9H8/4hd2gc9Tb2s0MwGHILL4kbbNm5tp3xw4ik7OYWNrj3m+nG6Xb -vKXh2xEanAZAyMXTqDJTHdn7/CEqusQPJjZGV+Mf1kjKu7p4qcXFnIXP5ILnTW7b -lHoWC4eweDzKOMRzXmbABEVSUvx2SmPl4TcoC5L1SCAHEmZaKbaY7S5l53u6gl0f -ULuQbt7Hr3THznlNFKkGT1/yVNt2QOm1emZd55LaNe8E7XsNSlhl0grYQ+Ue8Jba -x85OapltVjxM9wVCwbgFyi04ihdKHo9e+uYKeTGKv0hU5O7HEH1ev6t/s2u/UG6h -TqEsYrVp0CMHpt5uAF6nZyK6GZ/CHTxh/rz1hADMofem59+e6tVtjnPGA3EjnJT8 -BMOw/D2QIDxjxj2GUzz+YJp50ENhWrL9oSDkG2nzv4NVL77QIy+T/2/f4PgokUDO -QJjIfxPWE40cHGHpnQtZvEPoxP0H3T0YhmEVwuJxX3uaWOY/8Fa1c7Ln0SwWdfV5 -gYvJV8o6c3sumcq1O3agPDlHC5O4IxG7AZQ8CHRDyASogzfkY6P579ZOGYaO4al7 -WA1YIpsHs3/1f4SByMuWe0NVkFfvXckjpqGrBQpTmqQzk6baa0VQ0cwU3XlkwHac -WB/fQ4jylwFzZDcp5JAo53n6aU72zgNvDlGTNKwdXXZI5U3JPocH0AiZgFFWYJLd -63PJLDnjyE3i6XMVlxifXKkXVv0RYSz+ByS7Oz9aCgnQhNU8ycv+UxtfkPQih5zE -/0Y2EEFknajmFJpNXczzF8OEzaswmR0AOjcCiklZKRf61rf5faJxJhhqKEEBJuL6 -oodDVRk3OGU1yQSBazT8nK3V+e6FMo3tWkra2BXFCD+pKxTy014Cp59S1w6F1Fjt -WX7eMWSLWfQ56j2kLMBHq5gb2arqlqH3fsYOTD3TNjCYF3Sgx309kVPuOK5vw61P -pnL/LN3iGY42WR+9lfAyNN2qj9zvwKwscyYs5+DPQoPmcPcVGc3v/u66bLcOGbEU -OlGa/6gdD4GCp5E4fP/7GbnEY/PW2abquFhGB+pVdl3/4+1U/8kItlfWNZoG4FhE -gjMd7glmrdFiNJFFpf5ks1lVXGqJ4mZxqtEZrxUEwciZjm4V27a+E2KyV9NnksZ6 -xF4tGPKIPsvNTV5o8ZqjiacxgbYmr2ywqDXKCgpU/RWSh1sLapqSQqbH/w0MquUj -VhVX0RMYH/foKtjagZf/KO1/mnCITl86treIdachGgR4wr/qqMjrpPUaPLCRY3JQ -00XUP1Mu6YPE0SnMYAVxZheqKHly3a1pg4Xp7YWlM671oUORs3+VENfnbIxgr+2D -TiJT9PxwpfK53Oh7RBSWHJZRuAdLUXE8DG+bl0N/QkJM6pFUxTI1AQ== ------END RSA PRIVATE KEY----- diff --git a/the_big_refactor_rcs.md b/the_big_refactor_rcs.md new file mode 100644 index 0000000..e59076f --- /dev/null +++ b/the_big_refactor_rcs.md @@ -0,0 +1,149 @@ +# The Big Refactor: An Honest Assessment + +**Date:** 2026-02-09 +**Reviewer:** RCS Claude session (post-refactor) +**Scope:** Phases 1-8 of the rh-trex library extraction and RCS consumption + +--- + +## What Happened + +Registry-credentials-service started life as a fork of rh-trex, a Red Hat microservice template. Like all forks, it carried the full weight of the upstream codebase — ~13,700 lines of framework Go code that RCS didn't write, didn't own, and would never modify. Authentication middleware, database session management, GORM migrations, Cobra CLI wiring, Prometheus metrics, Sentry integration, health checks, CORS configuration, structured logging — all copy-pasted into RCS's tree. + +Over 8 phases (all executed in a single day, 2026-02-09), two coordinated Claude sessions refactored rh-trex from a "clone-and-own" template into a proper Go library, then migrated RCS from a full fork to a thin consumer. + +The numbers tell the story: + +| Metric | Before | After | Delta | +|--------|--------|-------|-------| +| Go files touched | 154 | — | — | +| Lines inserted | — | 267 | — | +| Lines deleted | — | 12,086 | — | +| **Net line change** | — | — | **-11,819** | +| Total Go files | ~120+ | 60 | -50% | +| `cmd/` files | 25 | 3 | -88% | +| Framework packages owned | ~15 | 0 | -100% | + +RCS went from owning a copy of the entire rh-trex framework to importing it. The codebase now has 60 Go files and 10,274 lines total. Of those, ~2,400 are generated OpenAPI client code and ~1,750 are entity plugin code (the actual domain logic). The rest is test infrastructure, templates, and the handful of wiring files. + +--- + +## What Went Right + +### 1. The extraction was architecturally sound + +The layered approach — parameterize first (Phase 1), then extract entities into plugins (Phase 2), then migrate imports (Phase 3), then progressively extract `cmd/` infrastructure (Phases 5-8) — was the correct order. Each phase built on a stable foundation. At no point was the codebase in a half-migrated state where tests couldn't run. + +The plugin architecture that emerged is genuinely good. Each entity (registryCredentials, accessTokens, registrys) is a self-contained 8-file package that registers itself via `init()`. The registration covers four concerns — service, routes, controllers, and migrations — and the framework discovers them automatically. Adding a new entity is one generator command, zero manual wiring. This is a real improvement over the original pattern. + +### 2. The separation of concerns is clean + +After the refactor, RCS's `main.go` is 30 lines. Its `framework.go` is 25 lines. The entire `cmd/` directory is 3 files. Everything else is either domain-specific plugin code or test infrastructure. There is no framework code to maintain, drift, or debug. When rh-trex fixes a bug in JWT validation or GORM session handling, RCS gets it for free. + +### 3. The coordination mechanism worked + +Using `trex_comms.md` as an async communication channel between two Claude sessions was unconventional but effective. The proposal-review-implementation-acknowledgment cadence prevented both sessions from making incompatible changes. The structured handoff blocks (introduced mid-session) improved signal quality. The comms file itself serves as a complete architectural decision record. + +### 4. Tests remained green throughout + +Every phase ended with `go build ./...` passing. Most phases ended with integration tests passing. The few test failures that did occur were diagnosed and fixed within the same session (gofmt ordering, external test package for import cycles, corrupted base64 JWT key). The one pre-existing flaky test (`TestRegistryCredentialListSearch`) was identified as a pre-existing bug and fixed (value receiver on `ResetDB()`). + +--- + +## What Went Wrong (or Could Have Gone Better) + +### 1. Nothing is committed + +This is the elephant in the room. Eight phases of refactoring, ~12,000 lines deleted, 193 files changed — and it's all sitting in the working tree. On both sides. rh-trex has equally massive uncommitted changes on a `trex_library` branch. A single `git checkout .` would erase everything. + +The work should have been committed incrementally. Phase 1 should have been a commit. Phase 3 should have been a commit. The comms file explicitly called this out at the end, but by then 8 phases had already accumulated. This is the single biggest risk to the work done. + +### 2. The `replace` directive is a liability + +RCS depends on rh-trex via: +``` +replace github.com/openshift-online/rh-trex => ../../openshift-online/rh-trex +``` + +This means: +- RCS can only build if the rh-trex checkout exists at exactly that relative path +- CI/CD cannot resolve this without mirroring the same filesystem layout +- The dependency is unpinned — any change to rh-trex's working tree immediately affects RCS +- The rh-trex library changes are also uncommitted, making this a double-fragility + +This was necessary for rapid iteration during the refactoring, but it needs to be resolved before any of this ships. rh-trex needs to be published (tagged, pushed), and RCS needs to pin to a real version. + +### 3. Integration test coverage is uncertain + +Unit tests pass (1 test). Integration tests reportedly pass (19/19) but require: +- A running PostgreSQL instance via `make db/setup` (Podman) +- The `DB_FACTORY_MODE=external` environment variable +- The rh-trex local checkout in the correct relative path + +I could verify `make test` but not `make test-integration` in this session. The integration test status is based on the previous session's reporting. For a change of this magnitude, that's insufficient confidence. + +### 4. The comms file is too long + +`trex_comms.md` is 1,575 lines. It contains valuable architectural decisions but also contains extensive back-and-forth on proposals, detailed file listings, and implementation logs that were relevant in the moment but are noise now. The file's own header says "keep entries concise" and "only active/pending items get full detail" — this was aspirational, not actual. The completed phases section at the top is reasonably concise, but the raw proposal-response threads below it are not. + +### 5. The pace was aggressive + +All 8 phases were executed in a single day. This is impressive from a velocity standpoint but risky from a review standpoint. No human reviewed any of these changes between phases. The two Claude sessions reviewed each other's work, but they share the same biases and blind spots. A human reviewing 12,000 deleted lines and 267 added lines across 193 files would catch things that two AI sessions might not. + +### 6. Naming: `registrys` + +The entity is named `registrys` (with a `y` instead of the correct English plural `registries`). This was inherited from the original codebase and the generator's naive pluralization (append `s`). It's in table names, API paths (`/registry_credentials`), package names, and test files. Renaming it now would be a significant effort. It's a minor wart but it's the kind of thing that makes developers wince when they read the code for the first time. + +--- + +## Impact Assessment + +### Positive Impact + +**Maintenance burden eliminated.** RCS no longer needs to track changes in 15+ framework packages. Bug fixes, security patches, and performance improvements in rh-trex propagate automatically. This was the primary motivation and it was achieved completely. + +**Cognitive load reduced.** A developer opening this codebase sees 60 files, not 120+. The `cmd/` directory has 3 files, not 25. The mental model is simple: "plugins register themselves, the framework runs them." There's no need to understand the authentication middleware, database transaction handling, or metrics collection to work on a new entity. + +**Entity creation is trivial.** `go run ./scripts/generator.go --kind NewThing --fields "name:string:required,count:int"` generates a complete, tested, working entity with CRUD endpoints, event-driven controllers, database migration, and integration tests. Zero manual wiring. + +**Template sync is solved.** Both projects share the same 13 templates. When one improves (e.g., better patch handling, advisory locks), the other benefits. The intentional difference (RCS templates hardcode rh-trex import paths for framework code) is documented and correct. + +### Negative Impact + +**Hard coupling to rh-trex.** RCS is now deeply dependent on rh-trex's internal package structure. If rh-trex renames `pkg/environments` to `pkg/env`, RCS breaks. If rh-trex changes the `EnvironmentImpl` interface, RCS breaks. If rh-trex removes `NewDefaultAPIServer`, RCS breaks. This is the trade-off of choosing "import the library" over "own the code." It's the right trade-off for a project that's co-developed with the library, but it would be problematic for an independent consumer. + +**Local development requires rh-trex checkout.** The `replace` directive means every developer, CI system, and deployment pipeline needs both repositories checked out in a specific relative path structure. This is fine for a small team sharing a dev environment but scales poorly. + +**Debugging gets harder.** When something goes wrong in the authentication middleware or database transaction handling, the developer has to navigate into the rh-trex codebase to understand what's happening. The code is no longer "right there" — it's in a different repository. Stack traces will reference rh-trex package paths. + +### Value Delivered + +The refactor delivered exactly what it promised: RCS is now a thin domain-specific layer on top of a shared framework. The 88% reduction in `cmd/` files and 100% elimination of framework package ownership are the clearest measures of success. + +However, **zero business value was delivered.** RCS has the same 3 entities (registryCredentials, accessTokens, registrys) with the same CRUD operations as before. No new features. No closer to the AMS migration. No registry credential pool management. No Quay.io integration. The refactor was pure infrastructure work — necessary infrastructure work, but infrastructure nonetheless. + +The `ams-migration.md` plan describes 12 weeks of actual feature work. That hasn't started. The refactor was a prerequisite for that work (maintaining a forked framework while building complex new features would have been painful), but it consumed an entire session day on its own. + +--- + +## Recommendations + +1. **Commit immediately.** Create a feature branch, commit all changes with a clear message describing the 8-phase migration, push it. Do the same on rh-trex's `trex_library` branch. The single biggest risk right now is losing this work. + +2. **Run integration tests before committing.** `make db/teardown && make db/setup && DB_FACTORY_MODE=external make test-integration`. If any test fails, fix it first. + +3. **Plan the `replace` directive resolution.** Before merging to main, rh-trex needs a tagged release and RCS needs to pin to it. The local replace is fine for development but not for production. + +4. **Start domain work.** The AMS migration is the actual deliverable. The refactor enables it; it doesn't replace it. + +5. **Collapse the comms file.** The detailed proposal-response threads in `trex_comms.md` have served their purpose. Collapse them into the history section. Keep only the current state and next steps as active content. + +--- + +## Final Verdict + +This was a well-executed, architecturally sound refactoring that achieved its goal: converting RCS from a diverging fork into a maintainable thin consumer. The coordination between two AI sessions was effective. The incremental approach prevented breakage. The plugin architecture that emerged is genuinely good. + +The work is at risk because it's uncommitted. The dependency model (`replace` directive) needs resolution. And the most important work — actual feature development — hasn't started yet. + +**Grade: B+.** The engineering is solid. The execution was disciplined. The risk management (not committing) was poor. The business impact is zero until domain features ship on top of this foundation.